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 = 0,
+ SingleBitThreeWire = 2,
+ TwoBitSerial = 4,
+ FourBitSerial = 6,
+}
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 = 0,
+ FR1 = 1,
+ FR2 = 2,
+ CFR = 3,
+ CFTW0 = 4,
+ CPOW0 = 5,
+ ACR = 6,
+ LSRR = 7,
+ RDW = 8,
+ FDW = 9,
+ CW1 = 10,
+ CW2 = 11,
+ CW3 = 12,
+ CW4 = 13,
+ CW5 = 14,
+ CW6 = 15,
+ CW7 = 16,
+ CW8 = 17,
+ CW9 = 18,
+ CW10 = 19,
+ CW11 = 20,
+ CW12 = 21,
+ CW13 = 22,
+ CW14 = 23,
+ CW15 = 24,
+}
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(/* private fields */);
Specifies an output channel of the AD9959 DDS chip.
+Get the underlying bits value.
+The returned value is exactly the bits set in this flags value.
+Convert from a bits value.
+This method will return None
if any unknown bits are set.
Convert from a bits value, unsetting any unknown bits.
+Convert from a bits value exactly.
+Get a flags value with the bits of a flag with the given name set.
+This method will return None
if name
is empty or doesn’t
+correspond to any named flag.
Whether any set bits in a source flags value are also set in a target flags value.
+Whether all set bits in a source flags value are also set in a target flags value.
+The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+remove
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
Call insert
when value
is true
or remove
when value
is false
.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
The bitwise negation (!
) of the bits in a flags value, truncating the result.
Yield a set of contained flags values.
+Each yielded flags value will correspond to a defined named flag. Any unknown bits +will be yielded together as a final flags value.
+Yield a set of contained named flags values.
+This method is like iter
, except only yields bits in contained named flags.
+Any unknown bits, or bits not corresponding to a contained flag will not be yielded.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
The bitwise exclusive-or (^
) of the bits in two flags values.
The bitwise or (|
) of the bits in each flags value.
extend_one
)extend_one
)|
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.Flags::insert
] when value
is true
or [Flags::remove
] when value
is false
.&
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.!
) of the bits in a flags value, truncating the result.The bitwise or (|
) of the bits in each flags value.
The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
pub struct ProfileSerializer { /* private fields */ }
Represents a means of serializing a DDS profile for writing to a stream.
+Update a number of channels with the requested profile.
+channels
- A set of channels to apply the configuration to.ftw
- If provided, indicates a frequency tuning word for the channels.pow
- If provided, indicates a phase offset word for the channels.acr
- If provided, indicates the amplitude control register for the channels. The ACR
+should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used,
+the “Amplitude multiplier enable” bit must be set.pub trait Interface {
+ type Error;
+
+ // Required methods
+ fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>;
+ fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
+ fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
+}
A trait that allows a HAL to provide a means of communicating with the AD9959.
+pub struct Context {}
Execution context
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
ethernet_link
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub fn __rtic_internal_ethernet_link_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_settings_update_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_start_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_telemetry_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_usb_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
fn ethernet_link(c: Context<'_>)
User SW task ethernet_link
+fn settings_update(c: Context<'_>)
User SW task settings_update
+pub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. Use method .lock()
to gain access
App module
+The RTIC application module
+pub use rtic::Monotonic as _;
ethernet_link
has access toidle
has access toprocess
has access toprocess
has access tosettings_update
has access tosettings_update
has access tostart
has access totelemetry
has access totelemetry
has access tousb
has access tousb
has access topub struct Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
Holds static methods for each monotonic.
+pub use Monotonic::now;
Monotonic::now()
Hardware task
+process
has access toprocess
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub iir_state: &'a mut [[[f32; 4]; 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 [[[f32; 4]; 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 {
+ usb_terminal: SerialTerminal,
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ iir_state: [[[f32; 4]; 1]; 2],
+ generator: FrameGenerator,
+ cpu_temp_sensor: CpuTempSensor,
+}
RTIC local resource struct
+usb_terminal: SerialTerminal
§sampling_timer: SamplingTimer
§digital_inputs: (DigitalInput0, DigitalInput1)
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§iir_state: [[[f32; 4]; 1]; 2]
§generator: FrameGenerator
§cpu_temp_sensor: CpuTempSensor
struct Shared {
+ usb: UsbDevice,
+ network: NetworkUsers<Settings, Telemetry, 3>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ signal_generator: [SignalGenerator; 2],
+}
RTIC shared resource struct
+usb: UsbDevice
§network: NetworkUsers<Settings, Telemetry, 3>
§settings: Settings
§telemetry: TelemetryBuffer
§signal_generator: [SignalGenerator; 2]
pub struct __rtic_internal_Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
pub struct __rtic_internal_eth_Context {}
Execution context
+pub struct __rtic_internal_ethernet_linkSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub struct __rtic_internal_ethernet_link_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_idleSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. 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 [[[f32; 4]; 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 [[[f32; 4]; 1]; 2]
Local resource iir_state
generator: &'a mut FrameGenerator
Local resource generator
pub struct __rtic_internal_processSharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_process_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_settings_updateLocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct __rtic_internal_settings_updateSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub signal_generator: signal_generator_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
signal_generator: signal_generator_that_needs_to_be_locked<'a>
Resource proxy resource signal_generator
. Use method .lock()
to gain access
pub struct __rtic_internal_settings_update_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_spi2_Context {}
Execution context
+pub struct __rtic_internal_spi3_Context {}
Execution context
+pub struct __rtic_internal_spi4_Context {}
Execution context
+pub struct __rtic_internal_spi5_Context {}
Execution context
+pub struct __rtic_internal_startLocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
pub struct __rtic_internal_start_Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct __rtic_internal_telemetryLocalResources<'a> {
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct __rtic_internal_telemetrySharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_telemetry_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_usbLocalResources<'a> {
+ pub usb_terminal: &'a mut SerialTerminal,
+}
Local resources usb
has access to
usb_terminal: &'a mut SerialTerminal
Local resource usb_terminal
pub struct __rtic_internal_usbSharedResources<'a> {
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. Use method .lock()
to gain access
pub struct __rtic_internal_usb_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
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
usb
has access tousb
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 usb_terminal: &'a mut SerialTerminal,
+}
Local resources usb
has access to
usb_terminal: &'a mut SerialTerminal
Local resource usb_terminal
pub struct SharedResources<'a> {
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. 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: [[Biquad<f32>; 1]; 2],
+ pub(crate) allow_hold: bool,
+ pub(crate) force_hold: bool,
+ pub(crate) telemetry_period: u16,
+ pub(crate) stream_target: StreamTarget,
+ pub(crate) signal_generator: [BasicConfig; 2],
+}
afe: [Gain; 2]
Configure the Analog Front End (AFE) gain.
+afe/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]Any of the variants of Gain enclosed in double quotes.
+iir_ch: [[Biquad<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::Biquad
+allow_hold: bool
Specified true if DI1 should be used as a “hold” input.
+allow_hold
“true” or “false”
+force_hold: bool
Specified true if “hold” should be forced regardless of DI1 state and hold allowance.
+force_hold
“true” or “false”
+telemetry_period: u16
Specifies the telemetry output period in seconds.
+telemetry_period
Any non-zero value less than 65536.
+stream_target: StreamTarget
§signal_generator: [BasicConfig; 2]
Specifies the config for signal generators to add on to DAC0/DAC1 outputs.
+signal_generator/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]indices
. Read moreRedirecting to ../../idsp/struct.Accu.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/all.html b/firmware/idsp/all.html new file mode 100644 index 0000000000..0e34dc6e4e --- /dev/null +++ b/firmware/idsp/all.html @@ -0,0 +1 @@ +Redirecting to ../../idsp/fn.atan2.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/struct.Complex.html b/firmware/idsp/complex/struct.Complex.html new file mode 100644 index 0000000000..f35ad587c7 --- /dev/null +++ b/firmware/idsp/complex/struct.Complex.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Complex.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/trait.ComplexExt.html b/firmware/idsp/complex/trait.ComplexExt.html new file mode 100644 index 0000000000..67c86964a4 --- /dev/null +++ b/firmware/idsp/complex/trait.ComplexExt.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.ComplexExt.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/complex/trait.MulScaled.html b/firmware/idsp/complex/trait.MulScaled.html new file mode 100644 index 0000000000..60a9d55ba3 --- /dev/null +++ b/firmware/idsp/complex/trait.MulScaled.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.MulScaled.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/cossin/fn.cossin.html b/firmware/idsp/cossin/fn.cossin.html new file mode 100644 index 0000000000..1b3c54abd4 --- /dev/null +++ b/firmware/idsp/cossin/fn.cossin.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/fn.cossin.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Cascade.html b/firmware/idsp/filter/struct.Cascade.html new file mode 100644 index 0000000000..1ef560069a --- /dev/null +++ b/firmware/idsp/filter/struct.Cascade.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Cascade.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Nyquist.html b/firmware/idsp/filter/struct.Nyquist.html new file mode 100644 index 0000000000..2dd1cfc2cd --- /dev/null +++ b/firmware/idsp/filter/struct.Nyquist.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Nyquist.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/struct.Repeat.html b/firmware/idsp/filter/struct.Repeat.html new file mode 100644 index 0000000000..6268dd5292 --- /dev/null +++ b/firmware/idsp/filter/struct.Repeat.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Repeat.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/filter/trait.Filter.html b/firmware/idsp/filter/trait.Filter.html new file mode 100644 index 0000000000..f40a9dd97f --- /dev/null +++ b/firmware/idsp/filter/trait.Filter.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.Filter.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/fn.atan2.html b/firmware/idsp/fn.atan2.html new file mode 100644 index 0000000000..95d325f136 --- /dev/null +++ b/firmware/idsp/fn.atan2.html @@ -0,0 +1,14 @@ +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 where i32::MIN
is -π and i32::MAX
is πThe cos and sin values of the provided phase as a (i32, i32)
+tuple. With a 7-bit deep LUT there is 9e-6 max and 4e-6 RMS error
+in each quadrature over 20 bit phase.
pub fn overflowing_sub<T>(y: T, x: T) -> (T, i32)where
+ T: WrappingSub + Zero + PartialOrd,
Subtract y - x
with signed overflow.
This is very similar to i32::overflowing_sub(y, x)
except that the
+overflow indicator is not a boolean but the signum of the overflow.
+Additionally it’s typically faster.
Returns:
+A tuple containg the (wrapped) difference y - x
and the signum of the
+overflow.
pub fn saturating_scale(lo: i32, hi: i32, shift: u32) -> i32
Combine high and low i32 into a single downscaled i32, saturating monotonically.
+Args:
+lo
: LSB i32 to scale down by shift
and range-extend with hi
+hi
: MSB i32 to scale up and extend lo
with. Output will be clipped if
+hi
exceeds the output i32 range.
+shift
: Downscale lo
by that many bits. Values from 1 to 32 inclusive
+are valid.
pub const HBF_CASCADE_BLOCK: usize = _; // 64usize
Max low-rate block size (HbfIntCascade input, HbfDecCascade output)
+pub const HBF_PASSBAND: f32 = 0.4;
Passband width in units of lowest sample rate
+pub const HBF_TAPS_98: ([f32; 15], [f32; 6], [f32; 3], [f32; 3], [f32; 2]);
Standard/optimal half-band filter cascade taps
+2*signal.remez(4*n - 1, bands=(0, .5-df/2, .5+df/2, 1), desired=(1, 0), fs=2, grid_density=512)[:2*n:2]
Half-band filters and cascades
+Used to perform very efficient high-dynamic range rate changes by powers of two.
+HBF_TAPS_98
.pub struct HbfDec<'a, T, const M: usize, const N: usize> { /* private fields */ }
Half band decimator (decimate by two)
+The effective number of DSP taps is 4*M - 1.
+M: number of taps +N: state size: N = 2*M - 1 + output.len()
+pub struct HbfDecCascade { /* private fields */ }
Half-band decimation filter cascade with optimal taps
+See HBF_TAPS. +Only in-place processing is implemented. +Supports rate changes of 1, 2, 4, 8, and 16.
+source
. Read morepub struct HbfInt<'a, T, const M: usize, const N: usize> { /* private fields */ }
Half band interpolator (interpolation rate 2)
+The effective number of DSP taps is 4*M - 1.
+M: number of taps +N: state size: N = 2*M - 1 + input.len()
+pub struct HbfIntCascade { /* private fields */ }
Half-band interpolation filter cascade with optimal taps.
+This is a no_alloc version without trait objects. +The price to pay is fixed and maximal memory usage independent +of block size and cascade length.
+See HBF_TAPS. +Only in-place processing is implemented. +Supports rate changes of 1, 2, 4, 8, and 16.
+source
. Read morepub struct SymFir<'a, T, const M: usize, const N: usize> { /* private fields */ }
Symmetric FIR filter prototype.
+M
: number of taps, one-sided. The filter has effectively 2*M DSP tapsN
: state size: N = 2*M - 1 + {input/output}.len()Half-band filters (rate change of 2) and cascades of HBFs are implemented in
+HbfDec
and HbfInt
etc.
+The half-band filter has unique properties that make it preferrable in many cases:
The implementations here are all no_std
and no-alloc
.
+They support (but don’t require) in-place filtering to reduce memory usage.
+They unroll and optimmize extremely well targetting current architectures,
+e.g. requiring less than 4 instructions per input item for the full HbfDecCascade
on Skylake.
+The filters are optimized for decent block sizes and perform best (i.e. with negligible
+overhead) for blocks of 32 high-rate items or more, depending very much on architecture.
Create a new SymFir
.
taps
: one-sided FIR coefficients, expluding center tap, oldest to one-before-centerObtain a mutable reference to the input items buffer space.
+Perform the FIR convolution and yield results iteratively.
+Move items as new filter state.
+offset
: Keep the 2*M-1
items at offset
as the new filter state.pub trait Filter {
+ type Item;
+
+ // Required methods
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item]
+ ) -> &'a mut [Self::Item];
+ fn block_size(&self) -> (usize, usize);
+ fn response_length(&self) -> usize;
+}
Filter input items into output items.
+Process a block of items.
+Input items can be either in x
or in y
.
+In the latter case the filtering operation is done in-place.
+Output is always written into y
.
+The slice of items written into y
is returned.
+Input and output size relations must match the filter requirements
+(decimation/interpolation and maximum block size).
+When using in-place operation, y
needs to contain the input items
+(fewer than y.len()
in the case of interpolation) and must be able to
+contain the output items.
Return the block size granularity and the maximum block size.
+For in-place processing, this refers to constraints on y
.
+Otherwise this refers to the larger of x
and y
(x
for decimation and y
for interpolation).
+The granularity is also the rate change in the case of interpolation/decimation filters.
Finite impulse response length in numer of output items minus one +Get this many to drain all previous memory
+Redirecting to ../../../idsp/iir/struct.Biquad.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/iir/coefficients/struct.Filter.html b/firmware/idsp/iir/coefficients/struct.Filter.html new file mode 100644 index 0000000000..338665ed3a --- /dev/null +++ b/firmware/idsp/iir/coefficients/struct.Filter.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../idsp/iir/struct.Filter.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/iir/enum.Action.html b/firmware/idsp/iir/enum.Action.html new file mode 100644 index 0000000000..247a200313 --- /dev/null +++ b/firmware/idsp/iir/enum.Action.html @@ -0,0 +1,34 @@ +pub enum Action {
+ Kii = 0,
+ Ki = 1,
+ Kp = 2,
+ Kd = 3,
+ Kdd = 4,
+}
PID action
+This enumerates the five possible PID style actions of a crate::iir::Biquad
Double integrating, -40 dB per decade
+Integrating, -20 dB per decade
+Proportional
+Derivative=, 20 dB per decade
+Double derivative, 40 dB per decade
+self
and other
) and is used by the <=
+operator. Read more#[non_exhaustive]pub enum PidError {
+ OrderRange,
+}
Pid::build()
errors
The action gains cover more than three successive orders
+self
and other
) and is used by the <=
+operator. Read moreRedirecting to ../../../idsp/iir/enum.Action.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/iir/pid/enum.PidError.html b/firmware/idsp/iir/pid/enum.PidError.html new file mode 100644 index 0000000000..726c325221 --- /dev/null +++ b/firmware/idsp/iir/pid/enum.PidError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../idsp/iir/enum.PidError.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/iir/pid/struct.Pid.html b/firmware/idsp/iir/pid/struct.Pid.html new file mode 100644 index 0000000000..131f1e6770 --- /dev/null +++ b/firmware/idsp/iir/pid/struct.Pid.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../idsp/iir/struct.Pid.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/iir/sidebar-items.js b/firmware/idsp/iir/sidebar-items.js new file mode 100644 index 0000000000..732d1ab959 --- /dev/null +++ b/firmware/idsp/iir/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Action","PidError"],"struct":["Biquad","Filter","Pid"]}; \ No newline at end of file diff --git a/firmware/idsp/iir/struct.Biquad.html b/firmware/idsp/iir/struct.Biquad.html new file mode 100644 index 0000000000..f65e8c0795 --- /dev/null +++ b/firmware/idsp/iir/struct.Biquad.html @@ -0,0 +1,256 @@ +pub struct Biquad<T> { /* private fields */ }
Biquad IIR filter
+A biquadratic IIR filter supports up to two zeros and two poles in the transfer function. +It can be used to implement a wide range of responses to input signals.
+The Biquad performs the following operation to compute a new output sample y0
from a new
+input sample x0
given its configuration and previous samples:
y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)
This implementation here saves storage and improves caching opportunities by decoupling +filter configuration (coefficients, limits and offset) from filter state +and thus supports both (a) sharing a single filter between multiple states (“channels”) and (b) +rapid switching of filters (tuning, transfer) for a given state without copying either +state of configuration.
+Direct Form 1 (DF1) and Direct Form 2 transposed (DF2T) are the only IIR filter +structures with an (effective bin the case of TDF2) single summing junction +this allows clamping of the output before feedback.
+DF1 allows atomic coefficient change because only inputs and outputs are pipelined. +The summing junctuion pipelining of TDF2 would require incremental +coefficient changes and is thus less amenable to online tuning.
+DF2T needs less state storage (2 instead of 4). This is in addition to the coefficient +storage (5 plus 2 limits plus 1 offset)
+DF2T is less efficient and accurate for fixed-point architectures as quantization
+happens at each intermediate summing junction in addition to the output quantization. This is
+especially true for common i64 + i32 * i32 -> i64
MACC architectures.
+One could use wide state storage for fixed point DF2T but that would negate the storage
+and processing advantages.
ba: [T; 5] = [b0, b1, b2, a1, a2]
is the coefficients type.
+To represent the IIR coefficients, this contains the feed-forward
+coefficients b0, b1, b2
followed by the feed-back coefficients
+a1, a2
, all five normalized such that a0 = 1
.
The summing junction of the filter also receives an offset u
.
The filter applies clamping such that min <= y <= max
.
See crate::iir::Filter
and crate::iir::Pid
for ways to generate coefficients.
Coefficient scaling (see Coefficient
) is fixed and optimized such that -2 is exactly
+representable. This is tailored to low-passes, PID, II etc, where the integration rule is
+[1, -2, 1].
There are two guard bits in the accumulator before clamping/limiting. +While this isn’t enough to cover the worst case accumulator, it does catch many real world +overflow cases.
+To represent the IIR state (input and output memory) during Biquad::update()
+the DF1 state contains the two previous inputs and output [x1, x2, y1, y2]
+concatenated. Lower indices correspond to more recent samples.
In the DF2T case the state contains [b1*x1 + b2*x2 - a1*y1 - a2*y2, b2*x1 - a2*y1]
In the DF1 case with first order noise shaping, the state contains [x1, x2, y1, y2, e1]
+where e0
is the accumulated quantization error.
The IIR coefficients can be mapped to other transfer function +representations, for example PID controllers as described in +https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw and +https://arxiv.org/abs/1508.06319.
+Using a Biquad as a template for a PID controller achieves several important properties:
+A “hold” filter that ingests input and maintains output
+ +let mut xy = [0.0, 1.0, 2.0, 3.0];
+let x0 = 7.0;
+let y0 = Biquad::HOLD.update(&mut xy, x0);
+assert_eq!(y0, 2.0);
+assert_eq!(xy, [x0, 0.0, y0, y0]);
A unity gain filter
+ +let x0 = 3.0;
+let y0 = Biquad::IDENTITY.update(&mut [0.0; 4], x0);
+assert_eq!(y0, x0);
A filter with the given proportional gain at all frequencies
+ +let x0 = 2.0;
+let k = 5.0;
+let y0 = Biquad::proportional(k).update(&mut [0.0; 4], x0);
+assert_eq!(y0, x0 * k);
Filter coefficients
+IIR filter tap gains (ba
) are an array [b0, b1, b2, a1, a2]
such that
+[Biquad::update(&mut xy, x0)
] returns
+y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)
.
assert_eq!(Biquad::<i32>::IDENTITY.ba()[0], <i32 as Coefficient>::ONE);
+assert_eq!(Biquad::<i32>::HOLD.ba()[3], -<i32 as Coefficient>::ONE);
Mutable reference to the filter coefficients.
+See Biquad::ba()
.
let mut i = Biquad::default();
+i.ba_mut()[0] = <i32 as Coefficient>::ONE;
+assert_eq!(i, Biquad::IDENTITY);
Summing junction offset
+This offset is applied to the output y0
summing junction
+on top of the feed-forward (b
) and feed-back (a
) terms.
+The feedback samples are taken at the summing junction and
+thus also include (and feed back) this offset.
Set the summing junction offset
+See Biquad::u()
.
let mut i = Biquad::default();
+i.set_u(5);
+assert_eq!(i.update(&mut [0; 4], 0), 5);
Lower output limit
+Guaranteed minimum output value. +The value is inclusive. +The clamping also cleanly affects the feedback terms.
+For fixed point types, during the comparison, +the lowest two bits of value and limit are truncated.
+ +assert_eq!(Biquad::<i32>::default().min(), i32::MIN);
Set the lower output limit
+See Biquad::min()
.
let mut i = Biquad::default();
+i.set_min(4);
+assert_eq!(i.update(&mut [0; 4], 0), 4);
Upper output limit
+Guaranteed maximum output value. +The value is inclusive. +The clamping also cleanly affects the feedback terms.
+For fixed point types, during the comparison,
+the lowest two bits of value and limit are truncated.
+The behavior is as if those two bits were 0 in the case
+of min
and one in the case of max
.
assert_eq!(Biquad::<i32>::default().max(), i32::MAX);
Set the upper output limit
+See Biquad::max()
.
let mut i = Biquad::default();
+i.set_max(-5);
+assert_eq!(i.update(&mut [0; 4], 0), -5);
Compute the overall (DC/proportional feed-forward) gain.
+ +assert_eq!(Biquad::proportional(3.0).forward_gain(), 3.0);
The sum of the b
feed-forward coefficients.
Compute input-referred (x
) offset.
let mut i = Biquad::proportional(3);
+i.set_u(3);
+assert_eq!(i.input_offset(), <i32 as Coefficient>::ONE);
Convert input (x
) offset to equivalent summing junction offset (u
) and apply.
In the case of a “PID” controller the response behavior of the controller
+to the offset is “stabilizing”, and not “tracking”: its frequency response
+is exclusively according to the lowest non-zero crate::iir::Action
gain.
+There is no high order (“faster”) response as would be the case for a “tracking”
+controller.
let mut i = Biquad::proportional(3.0);
+i.set_input_offset(2.0);
+let x0 = 0.5;
+let y0 = i.update(&mut [0.0; 4], x0);
+assert_eq!(y0, (x0 + i.input_offset()) * i.forward_gain());
let mut i = Biquad::proportional(-<i32 as Coefficient>::ONE);
+i.set_input_offset(1);
+assert_eq!(i.u(), -1);
offset
: Input (x
) offset.Direct Form 1 Update
+Ingest a new input value into the filter, update the filter state, and
+return the new output. Only the state xy
is modified.
N=4
Direct Form 1xy
contains:
[x1, x2, y1, y2]
[x0, x1, y0, y1]
let mut xy = [0.0, 1.0, 2.0, 3.0];
+let x0 = 4.0;
+let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+assert_eq!(y0, x0);
+assert_eq!(xy, [x0, 0.0, y0, 2.0]);
N=5
Direct Form 1 with first order noise shapinglet mut xy = [1, 2, 3, 4, 5];
+let x0 = 6;
+let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+assert_eq!(y0, x0);
+assert_eq!(xy, [x0, 1, y0, 3, 5]);
xy
contains:
[x1, x2, y1, y2, e1]
[x0, x1, y0, y1, e0]
Note: This is only useful for fixed point filters.
+N=2
Direct Form 2 transposedNote: This is only useful for floating point filters.
+Don’t use this for fixed point: Quantization happens at each state store operation.
+Ideally the state would be [T::ACCU; 2]
but then for fixed point it would use equal amount
+of storage compared to DF1 for no gain in performance and loss in functionality.
+There are also no guard bits here.
xy
contains:
[b1*x1 + b2*x2 - a1*y1 - a2*y2, b2*x1 - a2*y1]
[b1*x0 + b2*x1 - a1*y0 - a2*y1, b2*x0 - a2*y0]
let mut xy = [0.0, 1.0];
+let x0 = 3.0;
+let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+assert_eq!(y0, x0);
+assert_eq!(xy, [1.0, 0.0]);
xy
- Current filter state.x0
- New input.The new output y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)
self
and other
) and is used by the <=
+operator. Read morepub struct Filter<T> { /* private fields */ }
Standard audio biquad filter builder
+ +Set crititcal frequency from absolute units.
+critical_frequency
: “Relevant” or “corner” or “center” frequency
+in the same units as sample_frequency
sample_frequency
: The sample frequency in the same units as critical_frequency
.
+E.g. both in SI Hertz or rad/s
.Set relative critical frequency
+f0
: Relative critical frequency in units of the sample frequency.
+Must be 0 <= f0 <= 0.5
.Set relative critical angular frequency
+w0
: Relative critical angular frequency.
+Must be 0 <= w0 <= π
. Defaults to 0.0
.Set linear shelf gain
+Used only for peaking
, highshelf
, lowshelf
filters.
a
: Linear shelf gain. Defaults to 1.0
.Set shelf gain in dB
+Used only for peaking
, highshelf
, lowshelf
filters.
a_db
: Linear shelf gain. Defaults to 0.0
.Set inverse Q parameter of the filter
+The inverse “steepness”/“narrowness” of the filter transition.
+Defaults sqrt(2)
which is as steep as possible without overshoot.
qi
: Inverse Q parameter.Set Q parameter of the filter
+The “steepness”/“narrowness” of the filter transition.
+Defaults 1/sqrt(2)
which is as steep as possible without overshoot.
This affects the same parameter as bandwidth()
and shelf_slope()
.
+Use only one of them.
q
: Q parameter.Set the relative bandwidth
+This affects the same parameter as inverse_q()
and shelf_slope()
.
+Use only one of them.
bw
: Bandwidth in octavesSet the shelf slope.
+This affects the same parameter as inverse_q()
and bandwidth()
.
+Use only one of them.
s
: Shelf slope. A slope of 1.0
is maximally steep without overshoot.Low pass filter
+Builds second order biquad low pass filter coefficients.
+ +use idsp::iir::*;
+let ba = Filter::default()
+ .critical_frequency(0.1)
+ .gain(1000.0)
+ .lowpass();
+let iir = Biquad::<i32>::from(&ba);
+let mut xy = [0; 4];
+let x = vec![3, -4, 5, 7, -3, 2];
+let y: Vec<_> = x.iter().map(|x0| iir.update(&mut xy, *x0)).collect();
+assert_eq!(y, [5, 3, 9, 25, 42, 49]);
High pass filter
+Builds second order biquad high pass filter coefficients.
+ +use idsp::iir::*;
+let ba = Filter::default()
+ .critical_frequency(0.1)
+ .gain(1000.0)
+ .highpass();
+let iir = Biquad::<i32>::from(&ba);
+let mut xy = [0; 4];
+let x = vec![3, -4, 5, 7, -3, 2];
+let y: Vec<_> = x.iter().map(|x0| iir.update(&mut xy, *x0)).collect();
+assert_eq!(y, [5, -9, 11, 12, -1, 17]);
Band pass
+ +use idsp::iir::*;
+let ba = Filter::default()
+ .frequency(1000.0, 48e3)
+ .q(5.0)
+ .gain_db(3.0)
+ .bandpass();
+println!("{ba:?}");
An allpass filter
+Has constant gain
at all frequency but a variable phase shift.
A peaking/dip filter
+Has gain*shelf_gain
at critical frequency and gain
elsewhere.
Low shelf
+Approaches gain*shelf_gain
below critical frequency and gain
above.
use idsp::iir::*;
+let ba = Filter::default()
+ .frequency(1000.0, 48e3)
+ .shelf_slope(2.0)
+ .shelf_db(20.0)
+ .lowshelf();
+println!("{ba:?}");
self
and other
) and is used by the <=
+operator. Read morepub struct Pid<T> { /* private fields */ }
PID controller builder
+Builds Biquad
from action gains, gain limits, input offset and output limits.
let b: Biquad<f32> = Pid::default()
+ .period(1e-3)
+ .gain(Action::Ki, 1e-3)
+ .gain(Action::Kp, 1.0)
+ .gain(Action::Kd, 1e2)
+ .limit(Action::Ki, 1e3)
+ .limit(Action::Kd, 1e1)
+ .build()
+ .unwrap()
+ .into();
Gain for a given action
+Gain units are output/input * time.powi(order)
where
output
are output (y
) unitsinput
are input (x
) unitstime
are sample period units, e.g. SI secondsorder
is the action order: the frequency exponent
+(-1
for integrating, 0
for proportional, etc.)Note that inverse time units correspond to angular frequency units. +Gains are accurate in the low frequency limit. Towards Nyquist, the +frequency response is warped.
+ +let tau = 1e-3;
+let ki = 1e-4;
+let i: Biquad<f32> = Pid::default()
+ .period(tau)
+ .gain(Action::Ki, ki)
+ .build()
+ .unwrap()
+ .into();
+let x0 = 5.0;
+let y0 = i.update(&mut [0.0; 4], x0);
+assert!((y0 / (x0 * ki / tau) - 1.0).abs() < 2.0 * f32::EPSILON);
action
: Action to controlgain
: Gain valueGain limit for a given action
+Gain limit units are output/input
. See also Pid::gain()
.
+Multiple gains and limits may interact and lead to peaking.
let ki_limit = 1e3;
+let i: Biquad<f32> = Pid::default()
+ .gain(Action::Ki, 8.0)
+ .limit(Action::Ki, ki_limit)
+ .build()
+ .unwrap()
+ .into();
+let mut xy = [0.0; 4];
+let x0 = 5.0;
+for _ in 0..1000 {
+ i.update(&mut xy, x0);
+}
+let y0 = i.update(&mut xy, x0);
+assert!((y0 / (x0 * ki_limit) - 1.0f32).abs() < 1e-3);
action
: Action to limit in gainlimit
: Gain limitPerform checks, compute coefficients and return Biquad
.
No attempt is made to detect NaNs, non-finite gains, non-positive period, +zero gain limits, or gain/limit sign mismatches. +These will consequently result in NaNs/infinities, peaking, or notches in +the Biquad coefficients.
+Gain limits for zero gain actions or for proportional action are ignored.
+ +let i: Biquad<f32> = Pid::default().gain(Action::Kp, 3.0).build().unwrap().into();
+assert_eq!(i, Biquad::proportional(3.0));
Will panic in debug mode on fixed point coefficient overflow.
+self
and other
) and is used by the <=
+operator. Read moreThis crate contains some tuned DSP algorithms for general and especially embedded use. +Many of the algorithms are implemented on integer (fixed point) datatypes.
+One comprehensive user for these algorithms is Stabilizer.
+cossin()
uses a small (128 element or 512 byte) LUT, smart octant (un)mapping, linear interpolation and comprehensive analysis of corner cases to achieve a very clean signal (4e-6 RMS error, 9e-6 max error, 108 dB SNR typ), low spurs, and no bias with about 40 cortex-m instruction per call. It computes both cosine and sine (i.e. the complex signal) at once given a phase input.
atan2()
returns a phase given a complex signal (a pair of in-phase/x
/cosine and quadrature/y
/sine). The RMS phase error is less than 5e-6 rad, max error is less than 1.2e-5 rad, i.e. 20.5 bit RMS, 19.1 bit max accuracy. The bias is minimal.
PLL
, RPLL
: High accuracy, zero-assumption, fully robust, forward and reciprocal PLLs with dynamically adjustable time constant and arbitrary (in the Nyquist sampling sense) capture range, and noise shaping.
Unwrapper
, Accu
, saturating_scale()
Unwrapper
, Accu
, saturating_scale()
:
+Tools to handle, track, and unwrap phase signals or generate them.
iir::Biquad
are fixed point (i8
, i16
, i32
, i64
) and floating point (f32
, f64
) biquad IIR filters.
+Robust and clean clipping and offset (anti-windup, no derivative kick, dynamically adjustable gains and gain limits) suitable for PID controller applications.
+Three kinds of filter actions: Direct Form 1, Direct Form 2 Transposed, and Direct Form 1 with noise shaping supported.
+Coefficient sharing for multiple channels.
This is a rough feature comparison of several available biquad
crates, with no claim for completeness, accuracy, or even fairness.
+TL;DR: idsp
is slower but offers more features.
Feature\Crate | biquad-rs | fixed-filters | idsp::iir |
---|---|---|---|
Floating point f32 /f64 | ✅ | ❌ | ✅ |
Fixed point i32 | ❌ | ✅ | ✅ |
Parametric fixed point i32 | ❌ | ✅ | ❌ |
Fixed point i8 /i16 /i64 /i128 | ❌ | ❌ | ✅ |
DF2T | ✅ | ❌ | ✅ |
Limiting/Clamping | ❌ | ✅ | ✅ |
Fixed point accumulator guard bits | ❌ | ❌ | ✅ |
Summing junction offset | ❌ | ❌ | ✅ |
Fixed point noise shaping | ❌ | ❌ | ✅ |
Configuration/state decoupling/multi-channel | ❌ | ❌ | ✅ |
f32 parameter audio filter builder | ✅ | ✅ | ✅ |
f64 parameter audio filter builder | ✅ | ❌ | ✅ |
Additional filters (I/HO) | ❌ | ❌ | ✅ |
f32 PI builder | ❌ | ✅ | ✅ |
f32/f64 PI²D² builder | ❌ | ❌ | ✅ |
PI²D² builder limits | ❌ | ❌ | ✅ |
Support for fixed point a1=-2 | ❌ | ❌ | ✅ |
Three crates have been compared when processing 4x1M samples (4 channels) with a biquad lowpass.
+Hardware was thumbv7em-none-eabihf
, cortex-m7
, code in ITCM, data in DTCM, caches enabled.
Crate | Type, features | Cycles per sample |
---|---|---|
biquad-rs | f32 | 11.4 |
idsp::iir | f32 , limits, offset | 15.5 |
fixed-filters | i32 , limits | 20.3 |
idsp::iir | i32 , limits, offset | 23.5 |
idsp::iir | i32 , limits, offset, noise shaping | 30.0 |
svf
is a simple IIR state variable filter simultaneously providing highpass, lowpass,
+bandpass, and notch filtering of a signal.
Lowpass
, Lockin
Lowpass
, Lockin
are fast, infinitely cascadable, first- and second-order lowpass and the corresponding integration into a lockin amplifier algorithm.
hbf::HbfDec
, hbf::HbfInt
, hbf::HbfDecCascade
, hbf::HbfIntCascade
:
+Fast f32
symmetric FIR filters, optimized half-band filters, half-band filter decimators and integators and cascades.
+These are used in stabilizer-stream
for online PSD calculation on log
+frequency scale for arbitrarily large amounts of data.
y - x
with signed overflow.Redirecting to ../../idsp/struct.Lockin.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/struct.Lowpass.html b/firmware/idsp/lowpass/struct.Lowpass.html new file mode 100644 index 0000000000..6032756d16 --- /dev/null +++ b/firmware/idsp/lowpass/struct.Lowpass.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/struct.Lowpass.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/type.Lowpass1.html b/firmware/idsp/lowpass/type.Lowpass1.html new file mode 100644 index 0000000000..df807601a8 --- /dev/null +++ b/firmware/idsp/lowpass/type.Lowpass1.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/type.Lowpass1.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/lowpass/type.Lowpass2.html b/firmware/idsp/lowpass/type.Lowpass2.html new file mode 100644 index 0000000000..586164772e --- /dev/null +++ b/firmware/idsp/lowpass/type.Lowpass2.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/type.Lowpass2.html...
+ + + \ No newline at end of file diff --git a/firmware/idsp/num/trait.Coefficient.html b/firmware/idsp/num/trait.Coefficient.html new file mode 100644 index 0000000000..78a4cd4f34 --- /dev/null +++ b/firmware/idsp/num/trait.Coefficient.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../idsp/trait.Coefficient.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..392255bfb1 --- /dev/null +++ b/firmware/idsp/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["atan2","cossin","overflowing_sub","saturating_scale"],"mod":["hbf","iir","svf"],"struct":["Accu","Cascade","Complex","Lockin","Lowpass","Nyquist","PLL","RPLL","Repeat","Unwrapper"],"trait":["Coefficient","ComplexExt","Filter","MulScaled"],"type":["Lowpass1","Lowpass2"]}; \ No newline at end of file diff --git a/firmware/idsp/struct.Accu.html b/firmware/idsp/struct.Accu.html new file mode 100644 index 0000000000..175c00203c --- /dev/null +++ b/firmware/idsp/struct.Accu.html @@ -0,0 +1,200 @@ +pub struct Accu<T> { /* private fields */ }
Wrapping 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 moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)pub struct Cascade<T, U>(/* private fields */);
Combine two different filters in cascade
+#[repr(C)]pub struct Complex<T> {
+ pub re: T,
+ pub im: T,
+}
A complex number in Cartesian form.
+Complex<T>
is memory layout compatible with an array [T; 2]
.
Note that Complex<F>
where F is a floating point type is only memory
+layout compatible with C’s complex types, not necessarily calling
+convention compatible. This means that for FFI you can only pass
+Complex<F>
behind a pointer, not as a value.
Example of extern function declaration.
+ +use num_complex::Complex;
+use std::os::raw::c_int;
+
+extern "C" {
+ fn zaxpy_(n: *const c_int, alpha: *const Complex<f64>,
+ x: *const Complex<f64>, incx: *const c_int,
+ y: *mut Complex<f64>, incy: *const c_int);
+}
re: T
Real portion of the complex number
+im: T
Imaginary portion of the complex number
+Returns the L1 norm |re| + |im|
– the Manhattan distance from the origin.
+=
operation. Read more+=
operation. Read more+=
operation. Read more+=
operation. Read moreReturn a Complex on the unit circle given an angle.
+Example:
+ +use idsp::{Complex, ComplexExt};
+Complex::<i32>::from_angle(0);
+Complex::<i32>::from_angle(1 << 30); // pi/2
+Complex::<i32>::from_angle(-1 << 30); // -pi/2
Return the absolute square (the squared magnitude).
+Note: Normalization is 1 << 32
, i.e. U0.32.
Note(panic): This will panic for Complex(i32::MIN, i32::MIN)
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(i32::MIN, 0).abs_sqr(), 1 << 31);
+assert_eq!(Complex::new(i32::MAX, i32::MAX).abs_sqr(), u32::MAX - 3);
log2(power) re full scale approximation
+TODO: scale up, interpolate
+Panic:
+This will panic for Complex(i32::MIN, i32::MIN)
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(i32::MAX, i32::MAX).log2(), -1);
+assert_eq!(Complex::new(i32::MAX, 0).log2(), -2);
+assert_eq!(Complex::new(1, 0).log2(), -63);
+assert_eq!(Complex::new(0, 0).log2(), -64);
Return the angle.
+Note: Normalization is 1 << 31 == pi
.
Example:
+ +use idsp::{Complex, ComplexExt};
+assert_eq!(Complex::new(0, 0).arg(), 0);
/=
operation. Read more/=
operation. Read more/=
operation. Read more/=
operation. Read moreusize
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.isize
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u8
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u16
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u32
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u64
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i8
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i16
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i32
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.i64
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned.u128
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned. Read morei128
to return an optional value of this type. If the
+value cannot be represented by this type, then None
is returned. Read more*self = (*self * a) + b
*self = (*self * a) + b
*=
operation. Read more*=
operation. Read more*=
operation. Read more*=
operation. Read moreParses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of type T
radix
must be <= 18; larger radix would include i and j as digits,
+which cannot be supported.
The conversion returns an error if 18 <= radix <= 36; it panics if radix > 36.
+The elements of T
are parsed using Num::from_str_radix
too, and errors
+(or panics) from that are reflected here as well.
%=
operation. Read more%=
operation. Read more%=
operation. Read more%=
operation. Read more-=
operation. Read more-=
operation. Read more-=
operation. Read more-=
operation. Read moreself
to a usize
. If the value cannot be
+represented by a usize
, then None
is returned.self
to an isize
. If the value cannot be
+represented by an isize
, then None
is returned.self
to a u8
. If the value cannot be
+represented by a u8
, then None
is returned.self
to a u16
. If the value cannot be
+represented by a u16
, then None
is returned.self
to a u32
. If the value cannot be
+represented by a u32
, then None
is returned.self
to a u64
. If the value cannot be
+represented by a u64
, then None
is returned.self
to an i8
. If the value cannot be
+represented by an i8
, then None
is returned.self
to an i16
. If the value cannot be
+represented by an i16
, then None
is returned.self
to an i32
. If the value cannot be
+represented by an i32
, then None
is returned.self
to an i64
. If the value cannot be
+represented by an i64
, then None
is returned.self
to a u128
. If the value cannot be
+represented by a u128
(u64
under the default implementation), then
+None
is returned. Read moreself
to an i128
. If the value cannot be
+represented by an i128
(i64
under the default implementation), then
+None
is returned. Read morepub struct Lockin<T> { /* private fields */ }
Lockin filter
+Combines two Filter
and an NCO to perform demodulation
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. N must be 1
or 2
.
The filter will cleanly saturate towards the i32
range.
Both filters have been optimized for accuracy, dynamic range, and +speed on Cortex-M7.
+The filter configuration Config
contains the filter gains.
For the first-order lowpass this is a single element array [k]
with
+the corner frequency in scaled Q31:
+k = pi*(1 << 31)*f0/fn
where
+f0
is the 3dB corner frequency and
+fn
is the Nyquist frequency.
+The corner frequency is warped in the usual way.
For the second-order lowpass this is [k**2/(1 << 32), -k/q]
with q = 1/sqrt(2)
+for a Butterworth response.
In addition to the poles at the corner frequency the filters have zeros at Nyquist.
+The first-order lowpass works fine and accurate for any positive gain
+1 <= k <= (1 << 31) - 1
.
+The second-order lowpass works and is accurate for
+1 << 16 <= k <= q*(1 << 31)
.
pub struct Nyquist(/* private fields */);
Nyquist zero
+Filter with a flat transfer function and a transfer function zero at Nyquist.
+pub struct PLL { /* private fields */ }
Type-II, sampled phase, discrete time PLL
+This PLL tracks the frequency and phase of an input signal with respect to the sampling clock. +The open loop transfer function is I^2,I from input phase to output phase and P,I from input +phase to output frequency.
+The transfer functions (for phase and frequency) contain an additional zero at Nyquist.
+The PLL locks to any frequency (i.e. it locks to the alias in the first Nyquist zone) and is +stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop +bandwidth in octave steps. The gain can be changed freely between updates.
+The frequency and phase settling time constants for a frequency/phase jump are 1 << shift
+update cycles. The loop bandwidth is 1/(2*pi*(1 << shift))
in units of the sample rate.
+While the phase is being settled after settling the frequency, there is a typically very
+small frequency overshoot.
All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that +overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single +(T)-DF-{I,II} biquad/IIR) would break on overflow (i.e. every cycle).
+There are no floating point rounding errors here. But there is integer quantization/truncation
+error of the shift
lowest bits leading to a phase offset for very low gains. Truncation
+bias is applied. Rounding is “half up”. The phase truncation error can be removed very
+efficiently by dithering.
This PLL does not unwrap phase slips accumulated during (frequency) lock acquisition. +This can and should be implemented elsewhere by unwrapping and scaling the input phase +and un-scaling and wrapping output phase and frequency. This then affects dynamic range, +gain, and noise accordingly.
+The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to +increase resolution for extremely narrowband applications is obvious.
+This PLL implements first order noise shaping to reduce quantization errors.
+Update the PLL with a new phase sample. This needs to be called (sampled) periodically. +The signal’s phase/frequency is reconstructed relative to the sampling period.
+Args:
+x
: New input phase sample or None if a sample has been missed.k
: Feedback gain.Returns: +A tuple of instantaneous phase and frequency estimates.
+pub struct RPLL { /* private fields */ }
Reciprocal PLL.
+Consumes noisy, quantized timestamps of a reference signal and reconstructs
+the phase and frequency of the update() invocations with respect to (and in units of
+1 << 32 of) that reference.
+In other words, update()
rate ralative to reference frequency,
+u32::MAX
corresponding to both being equal.
Create a new RPLL instance.
+Args:
+Returns: +Initialized RPLL instance.
+Advance the RPLL and optionally supply a new timestamp.
+Args:
+update()
cycle (1 << dt2 counter cycles).shift_frequency
(see there).Returns: +A tuple containing the current phase (wrapping at the i32 boundary, pi) and +frequency.
+pub struct Repeat<const N: usize, T>(/* private fields */);
Repeat another filter
+pub struct Unwrapper<Q> { /* private fields */ }
Overflow unwrapper.
+This is unwrapping as in the phase and overflow unwrapping context, not
+unwrapping as in the Result
/Option
context.
Feed a new sample..
+Args:
+x
: New sampleReturns:
+The (wrapped) difference x - x_old
The current number of wraps
+The current phase
+pub struct State<T> {
+ pub lp: T,
+ pub hp: T,
+ pub bp: T,
+}
Second order state variable filter state
+lp: T
Lowpass output
+hp: T
Highpass output
+bp: T
Bandpass output
+pub struct Svf<T> { /* private fields */ }
State variable filter
+https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/
+Set the critical frequency
+In units of the sample frequency.
+self
and other
) and is used by the <=
+operator. Read morepub trait Coefficient: 'static + Copy + Num + AsPrimitive<Self::ACCU> {
+ type ACCU: AsPrimitive<Self> + Num;
+
+ const ONE: Self;
+ const NEG_ONE: Self;
+ const ZERO: Self;
+ const MIN: Self;
+ const MAX: Self;
+
+ // Required methods
+ fn macc(self, s: Self::ACCU, min: Self, max: Self, e1: Self) -> (Self, Self);
+ fn clip(self, min: Self, max: Self) -> Self;
+ fn mul_scaled(self, other: Self) -> Self;
+ fn div_scaled(self, other: Self) -> Self;
+ fn quantize<C>(value: C) -> Self
+ where Self: AsPrimitive<C>,
+ C: Float + AsPrimitive<Self>;
+}
Helper trait unifying fixed point and floating point coefficients/samples
+Accumulator type
+Proper scaling and potentially using a wide accumulator.
+Clamp self
such that min <= self <= max
.
+Undefined result if max < min
.
Clamp to between min and max
+Undefined if min > max
.
Multiplication (scaled)
+Division (scaled)
+Scale and quantize a floating point value.
+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.
+Unit magnitude from angle
+Staturating addition
+Saturating subtraction
+pub trait Filter {
+ type Config;
+
+ // Required methods
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32;
+ fn get(&self) -> i32;
+ fn set(&mut self, x: i32);
+}
Single inpout single output i32 filter
+pub trait MulScaled<T> {
+ // Required method
+ fn mul_scaled(self, other: T) -> Self;
+}
Full scale fixed point multiplication.
+Scaled multiplication for fixed point
+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/lockin/all.html b/firmware/lockin/all.html new file mode 100644 index 0000000000..c27fc03e38 --- /dev/null +++ b/firmware/lockin/all.html @@ -0,0 +1 @@ +pub struct Context {}
Execution context
+Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
ethernet_link
has access topub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub fn __rtic_internal_ethernet_link_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_settings_update_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_start_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_telemetry_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
pub fn __rtic_internal_usb_Monotonic_spawn_after(
+ duration: <Systick as Monotonic>::Duration
+) -> Result<SpawnHandle, ()>
Spawns the task after a set duration relative to the current time
+This will use the time Instant::new(0)
as baseline if called in #[init]
,
+so if you use a non-resetable timer use spawn_at
when in #[init]
fn ethernet_link(c: Context<'_>)
User SW task ethernet_link
+fn settings_update(c: Context<'_>)
User SW task settings_update
+pub struct Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. Use method .lock()
to gain access
App module
+The RTIC application module
+pub use rtic::Monotonic as _;
ethernet_link
has access toidle
has access toprocess
has access toprocess
has access tosettings_update
has access tosettings_update
has access tostart
has access totelemetry
has access totelemetry
has access tousb
has access tousb
has access topub struct Context<'a> {
+ pub core: Peripherals,
+ pub device: Peripherals,
+ pub cs: CriticalSection<'a>,
+}
Execution context
+core: Peripherals
Core (Cortex-M) peripherals
+device: Peripherals
Device peripherals
+cs: CriticalSection<'a>
Critical section token for init
+pub struct Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
Holds static methods for each monotonic.
+pub use Monotonic::now;
Monotonic::now()
Hardware task
+process
has access toprocess
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub adcs: &'a mut (Adc0Input, Adc1Input),
+ pub dacs: &'a mut (Dac0Output, Dac1Output),
+ pub lockin: &'a mut Lockin<Repeat<2, Lowpass<2>>>,
+ pub timestamper: &'a mut InputStamper,
+ pub pll: &'a mut RPLL,
+ pub generator: &'a mut FrameGenerator,
+ pub signal_generator: &'a mut SignalGenerator,
+}
Local resources process
has access to
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
lockin: &'a mut Lockin<Repeat<2, Lowpass<2>>>
Local resource lockin
timestamper: &'a mut InputStamper
Local resource timestamper
pll: &'a mut RPLL
Local resource pll
generator: &'a mut FrameGenerator
Local resource generator
signal_generator: &'a mut SignalGenerator
Local resource signal_generator
pub struct SharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
settings_update
has access tosettings_update
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct LocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct SharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
start
has access topub struct Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct LocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
struct Local {
+ usb_terminal: SerialTerminal,
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ timestamper: InputStamper,
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ pll: RPLL,
+ lockin: Lockin<Repeat<2, Lowpass<2>>>,
+ signal_generator: SignalGenerator,
+ generator: FrameGenerator,
+ cpu_temp_sensor: CpuTempSensor,
+}
RTIC local resource struct
+usb_terminal: SerialTerminal
§sampling_timer: SamplingTimer
§digital_inputs: (DigitalInput0, DigitalInput1)
§timestamper: InputStamper
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§pll: RPLL
§lockin: Lockin<Repeat<2, Lowpass<2>>>
§signal_generator: SignalGenerator
§generator: FrameGenerator
§cpu_temp_sensor: CpuTempSensor
struct Shared {
+ usb: UsbDevice,
+ network: NetworkUsers<Settings, Telemetry, 2>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+}
RTIC shared resource struct
+usb: UsbDevice
§network: NetworkUsers<Settings, Telemetry, 2>
§settings: Settings
§telemetry: TelemetryBuffer
pub struct __rtic_internal_Monotonics(pub Systick);
Monotonics used by the system
+0: Systick
pub struct __rtic_internal_eth_Context {}
Execution context
+pub struct __rtic_internal_ethernet_linkSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+}
Shared resources ethernet_link
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
pub struct __rtic_internal_ethernet_link_Context<'a> {
+ pub shared: SharedResources<'a>,
+}
Execution context
+Shared Resources this task has access to
+pub struct __rtic_internal_idleSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources idle
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. 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<Repeat<2, Lowpass<2>>>,
+ pub timestamper: &'a mut InputStamper,
+ pub pll: &'a mut RPLL,
+ pub generator: &'a mut FrameGenerator,
+ pub signal_generator: &'a mut SignalGenerator,
+}
Local resources process
has access to
adcs: &'a mut (Adc0Input, Adc1Input)
Local resource adcs
dacs: &'a mut (Dac0Output, Dac1Output)
Local resource dacs
lockin: &'a mut Lockin<Repeat<2, Lowpass<2>>>
Local resource lockin
timestamper: &'a mut InputStamper
Local resource timestamper
pll: &'a mut RPLL
Local resource pll
generator: &'a mut FrameGenerator
Local resource generator
signal_generator: &'a mut SignalGenerator
Local resource signal_generator
pub struct __rtic_internal_processSharedResources<'a> {
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources process
has access to
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_process_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_settings_updateLocalResources<'a> {
+ pub afes: &'a mut (AFE0, AFE1),
+}
Local resources settings_update
has access to
afes: &'a mut (AFE0, AFE1)
Local resource afes
pub struct __rtic_internal_settings_updateSharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+}
Shared resources settings_update
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
pub struct __rtic_internal_settings_update_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_startLocalResources<'a> {
+ pub sampling_timer: &'a mut SamplingTimer,
+}
Local resources start
has access to
sampling_timer: &'a mut SamplingTimer
Local resource sampling_timer
pub struct __rtic_internal_start_Context<'a> {
+ pub local: LocalResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+pub struct __rtic_internal_telemetryLocalResources<'a> {
+ pub digital_inputs: &'a mut (DigitalInput0, DigitalInput1),
+ pub cpu_temp_sensor: &'a mut CpuTempSensor,
+}
Local resources telemetry
has access to
digital_inputs: &'a mut (DigitalInput0, DigitalInput1)
Local resource digital_inputs
cpu_temp_sensor: &'a mut CpuTempSensor
Local resource cpu_temp_sensor
pub struct __rtic_internal_telemetrySharedResources<'a> {
+ pub network: network_that_needs_to_be_locked<'a>,
+ pub settings: settings_that_needs_to_be_locked<'a>,
+ pub telemetry: telemetry_that_needs_to_be_locked<'a>,
+}
Shared resources telemetry
has access to
network: network_that_needs_to_be_locked<'a>
Resource proxy resource network
. Use method .lock()
to gain access
settings: settings_that_needs_to_be_locked<'a>
Resource proxy resource settings
. Use method .lock()
to gain access
telemetry: telemetry_that_needs_to_be_locked<'a>
Resource proxy resource telemetry
. Use method .lock()
to gain access
pub struct __rtic_internal_telemetry_Context<'a> {
+ pub local: LocalResources<'a>,
+ pub shared: SharedResources<'a>,
+}
Execution context
+local: LocalResources<'a>
Local Resources this task has access to
+Shared Resources this task has access to
+pub struct __rtic_internal_usbLocalResources<'a> {
+ pub usb_terminal: &'a mut SerialTerminal,
+}
Local resources usb
has access to
usb_terminal: &'a mut SerialTerminal
Local resource usb_terminal
pub struct __rtic_internal_usbSharedResources<'a> {
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. Use method .lock()
to gain access
pub struct __rtic_internal_usb_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
Software task
+pub use Monotonic::spawn_after;
pub use Monotonic::spawn_at;
pub use Monotonic::SpawnHandle;
usb
has access tousb
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 usb_terminal: &'a mut SerialTerminal,
+}
Local resources usb
has access to
usb_terminal: &'a mut SerialTerminal
Local resource usb_terminal
pub struct SharedResources<'a> {
+ pub usb: usb_that_needs_to_be_locked<'a>,
+}
Shared resources usb
has access to
usb: usb_that_needs_to_be_locked<'a>
Resource proxy resource usb
. Use method .lock()
to gain access
pub(crate) const BATCH_SIZE: usize = _; // 8usize
pub(crate) const BATCH_SIZE_LOG2: u32 = 3;
pub(crate) const SAMPLE_TICKS: u32 = _; // 128u32
pub(crate) const SAMPLE_TICKS_LOG2: u32 = 7;
pub(crate) enum Conf {
+ Magnitude,
+ Phase,
+ ReferenceFrequency,
+ LogPower,
+ InPhase,
+ Quadrature,
+ Modulation,
+}
Output the lockin magnitude.
+Output the phase of the lockin
+Output the lockin reference frequency as a sinusoid
+Output the logarithmic power of the lockin
+Output the in-phase component of the lockin signal.
+Output the quadrature component of the lockin signal.
+Output the lockin internal modulation frequency as a sinusoid
+pub(crate) enum LockinMode {
+ Internal,
+ External,
+}
Utilize an internally generated reference for demodulation
+Utilize an external modulation signal supplied to DI0
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.The lockin
application implements a lock-in amplifier using either an external or internally
+generated reference.
Refer to the Settings structure for documentation of run-time configurable settings for this +application.
+Refer to Telemetry for information about telemetry reported by this application.
+This application streams raw ADC and DAC data over UDP. Refer to +stabilizer::net::data_stream for more information.
+pub struct Settings {
+ pub(crate) afe: [Gain; 2],
+ pub(crate) lockin_mode: LockinMode,
+ pub(crate) pll_tc: [u32; 2],
+ pub(crate) lockin_k: <Lowpass<2> as Filter>::Config,
+ pub(crate) lockin_harmonic: i32,
+ pub(crate) lockin_phase: i32,
+ pub(crate) output_conf: [Conf; 2],
+ pub(crate) telemetry_period: u16,
+ pub(crate) stream_target: StreamTarget,
+}
afe: [Gain; 2]
Configure the Analog Front End (AFE) gain.
+afe/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]Any of the variants of Gain enclosed in double quotes.
+lockin_mode: LockinMode
Specifies the operational mode of the lockin.
+lockin_mode
One of the variants of LockinMode enclosed in double quotes.
+pll_tc: [u32; 2]
Specifis the PLL time constant.
+pll_tc/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]The PLL time constant exponent (1-31).
+lockin_k: <Lowpass<2> as Filter>::Config
Specifies the lockin lowpass gains.
+lockin_k
The lockin low-pass coefficients. See idsp::Lowpass
for determining them.
lockin_harmonic: i32
Specifies which harmonic to use for the lockin.
+lockin_harmonic
Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate)
+lockin_phase: i32
Specifies the LO phase offset.
+lockin_phase
Demodulation LO phase offset. Units are in terms of i32, where i32::MIN is equivalent to +-pi and i32::MAX is equivalent to +pi.
+output_conf: [Conf; 2]
Specifies DAC output mode.
+output_conf/<n>
<n>
specifies which channel to configure. <n>
:= [0, 1]One of the variants of Conf enclosed in double quotes.
+telemetry_period: u16
Specifies the telemetry output period in seconds.
+telemetry_period
Any non-zero value less than 65536.
+stream_target: StreamTarget
indices
. Read more#[derive(TreeDeserialize)]
+{
+ // Attributes available to this derive:
+ #[tree]
+}
+
Derive the TreeDeserialize
trait for a struct.
#[derive(TreeSerialize)]
+{
+ // Attributes available to this derive:
+ #[tree]
+}
+
Derive the TreeSerialize
trait for a struct.
#[non_exhaustive]pub enum Error<E> {
+ Absent(usize),
+ TooShort(usize),
+ NotFound(usize),
+ TooLong(usize),
+ Inner(E),
+ PostDeserialization(E),
+}
Errors that can occur when using the Tree traits.
+A usize
member indicates the key depth where the error occurred.
+The depth here is the number of names or indices consumed.
+It is also the number of separators in a path or the length
+of an indices slice.
If multiple errors are applicable simultaneously the precedence +is from high to low:
+Absent > TooShort > NotFound > TooLong > Inner > PostDeserialization
+before any Ok
.
The key is valid, but does not exist at runtime.
+This is the case if an Option
using the Tree*
traits
+is None
at runtime. See also TreeKey
.
The key ends early and does not reach a leaf node.
+The key was not found (index unparsable or too large, name not found or invalid).
+The key is too long and goes beyond a leaf node.
+The value provided could not be serialized or deserialized +or the traversal function returned an error.
+There was an error after deserializing a value.
+The Deserializer
has encountered an error only after successfully
+deserializing a value. This is the case if there is additional unexpected data.
+The TreeDeserialize::deserialize_by_key()
update takes place but this
+error will be returned.
Miniconf
enables lightweight (no_std
) partial serialization (retrieval) and deserialization
+(updates, modification) within a tree by key. The tree is backed by
+structs/arrays/Options of serializable types.
See below for a comprehensive example showing the features of the Tree
traits.
+See also the documentation of the TreeKey
trait for a detailed description.
use miniconf::{Error, JsonCoreSlash, Tree, TreeKey};
+use serde::{Deserialize, Serialize};
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default)]
+enum Either {
+ #[default]
+ Bad,
+ Good,
+}
+
+#[derive(Deserialize, Serialize, Copy, Clone, Default, Tree)]
+struct Inner {
+ a: i32,
+ b: i32,
+}
+
+#[derive(Tree, Default)]
+struct Settings {
+ // Atomic updtes by field name
+ foo: bool,
+ enum_: Either,
+ struct_: Inner,
+ array: [i32; 2],
+ option: Option<i32>,
+
+ // Exclude an element (not Deserialize/Serialize)
+ #[tree(skip)]
+ skipped: (),
+
+ // Exposing elements of containers
+ // ... by field name
+ #[tree]
+ struct_tree: Inner,
+ // ... or by index
+ #[tree]
+ array_tree: [i32; 2],
+ // ... or exposing two levels (array index and then inner field name)
+ #[tree(depth(2))]
+ array_tree2: [Inner; 2],
+
+ // Hiding paths by setting the Option to `None` at runtime
+ #[tree]
+ option_tree: Option<i32>,
+ // Hiding a path and descending into the inner `Tree`
+ #[tree(depth(2))]
+ option_tree2: Option<Inner>,
+ // Hiding elements of an array of `Tree`s
+ #[tree(depth(3))]
+ array_option_tree: [Option<Inner>; 2],
+}
+
+let mut settings = Settings::default();
+let mut buf = [0; 64];
+
+// Atomic updates by field name
+settings.set_json("/foo", b"true")?;
+assert_eq!(settings.foo, true);
+settings.set_json("/enum_", br#""Good""#)?;
+settings.set_json("/struct_", br#"{"a": 3, "b": 3}"#)?;
+settings.set_json("/array", b"[6, 6]")?;
+settings.set_json("/option", b"12")?;
+settings.set_json("/option", b"null")?;
+
+// Deep access by field name in a struct
+settings.set_json("/struct_tree/a", b"4")?;
+// ... or by index in an array
+settings.set_json("/array_tree/0", b"7")?;
+// ... or by index and then struct field name
+settings.set_json("/array_tree2/1/b", b"11")?;
+
+// If a `Tree`-Option is `None` it is hidden at runtime and can't be serialized/deserialized.
+settings.option_tree = None;
+assert_eq!(settings.set_json("/option_tree", b"13"), Err(Error::Absent(1)));
+settings.option_tree = Some(0);
+settings.set_json("/option_tree", b"13")?;
+settings.option_tree2 = Some(Inner::default());
+settings.set_json("/option_tree2/a", b"14")?;
+settings.array_option_tree[1] = Some(Inner::default());
+settings.set_json("/array_option_tree/1/a", b"15")?;
+
+// Serializing elements by path
+let len = settings.get_json("/struct_", &mut buf).unwrap();
+assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);
+
+// Iterating over all paths
+for path in Settings::iter_paths::<String>("/") {
+ let path = path.unwrap();
+ // Serializing each
+ match settings.get_json(&path, &mut buf) {
+ // Full round-trip: deserialize and set again
+ Ok(len) => { settings.set_json(&path, &buf[..len])?; }
+ // Some settings are still `None` and thus their paths are expected to be absent
+ Err(Error::Absent(_)) => {}
+ e => { e.unwrap(); }
+ }
+}
+
One possible use of Miniconf
is a backend for run-time settings management in embedded devices.
+It was originally designed to work with JSON (serde_json_core
)
+payloads over MQTT (minimq
) and provides a MQTT settings management
+client and a Python reference implementation to interact with it.
Miniconf
can be used with any serde::Serializer
/serde::Deserializer
backend, path
+hierarchy separator, and key lookup algorithm.
Currently support for /
as the path hierarchy separator and JSON (serde_json_core
) is implemented
+through the JsonCoreSlash
super trait.
Miniconf is also protocol-agnostic. Any means that can receive key-value input from +some external source can be used to access nodes by path.
+The MqttClient
implements settings management over the MQTT
+protocol with JSON payloads. A Python reference library is provided that
+interfaces with it. This example discovers the unique prefix of an application listening to messages
+under the topic quartiq/application/12345
and set its foo
setting to true
.
python -m miniconf -d quartiq/application/+ foo=true
+
For structs Miniconf offers derive macros for TreeKey
, TreeSerialize
, and TreeDeserialize
.
+The macros implements the TreeKey
, TreeSerialize
, and TreeDeserialize
traits.
+Fields/items that form internal nodes (non-leaf) need to implement the respective Tree{Key,Serialize,Deserialize}
trait.
+Leaf fields/items need to support the respective serde
trait (and the desired serde::Serializer
/serde::Deserializer
+backend).
Structs, arrays, and Options can then be cascaded to construct more complex trees.
+When using the derive macro, the behavior and tree recursion depth can be configured for each
+struct field using the #[tree(depth(Y))]
attribute.
See also the TreeKey
trait documentation for details.
Lookup into the tree is done using an iterator over Key
items. usize
indices or &str
names are supported.
Path iteration is supported with arbitrary separator between names.
+Deferred/deep/non-atomic access to inner elements of some types is not yet supported, e.g. enums
+other than Option
. These are still however usable in their atomic serde
form as leaves.
mqtt-client
Enable the MQTT client feature. See the example in MqttClient
.json-core
Enable the JsonCoreSlash
implementation of serializing from and
+into json slices (using serde_json_core
).The mqtt-client
and json-core
features are enabled by default.
pub use minimq;
TreeKey
.TreeKey::indices()
.Result
up one hierarchy level, incrementing its usize member./
”.M: TreeKey
.TreeKey
, TreeSerialize
, and TreeDeserialize
traits for a struct.TreeDeserialize
trait for a struct.TreeKey
trait for a struct.TreeSerialize
trait for a struct.Redirecting to ../../miniconf/struct.PathIter.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/json_core/trait.JsonCoreSlash.html b/firmware/miniconf/json_core/trait.JsonCoreSlash.html new file mode 100644 index 0000000000..0a7572a19c --- /dev/null +++ b/firmware/miniconf/json_core/trait.JsonCoreSlash.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.JsonCoreSlash.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/mqtt_client/struct.MqttClient.html b/firmware/miniconf/mqtt_client/struct.MqttClient.html new file mode 100644 index 0000000000..aaf80d5d17 --- /dev/null +++ b/firmware/miniconf/mqtt_client/struct.MqttClient.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.MqttClient.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/sidebar-items.js b/firmware/miniconf/sidebar-items.js new file mode 100644 index 0000000000..b00d805fa6 --- /dev/null +++ b/firmware/miniconf/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"derive":["Tree","TreeDeserialize","TreeKey","TreeSerialize"],"enum":["Error"],"struct":["Metadata","MqttClient","PathIter","SliceShort"],"trait":["Increment","JsonCoreSlash","Key","TreeDeserialize","TreeKey","TreeSerialize"]}; \ No newline at end of file diff --git a/firmware/miniconf/struct.Metadata.html b/firmware/miniconf/struct.Metadata.html new file mode 100644 index 0000000000..9a22699447 --- /dev/null +++ b/firmware/miniconf/struct.Metadata.html @@ -0,0 +1,31 @@ +#[non_exhaustive]pub struct Metadata {
+ pub max_length: usize,
+ pub max_depth: usize,
+ pub count: usize,
+}
Metadata about a TreeKey namespace.
+Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.max_length: usize
The maximum length of a path in bytes.
+This is the concatenation of all names in a path
+and does not include separators.
+It includes paths that may be Error::Absent
at runtime.
max_depth: usize
The maximum path depth.
+This is equal to the maximum number of path hierarchy separators.
+It may be smaller than the Tree recursion depth const generic paramerter Y
.
+It includes paths that may be Error::Absent
at runtime.
count: usize
The total number of paths.
+This includes paths that may be Error::Absent
at runtime.
pub struct MqttClient<'buf, Settings, Stack, Clock, Broker, const Y: usize>where
+ Settings: TreeKey<Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: Clock,
+ Broker: Broker,{ /* private fields */ }
MQTT settings interface.
+The MQTT client places the TreeKey paths <path>
at the MQTT <prefix>/settings/<path>
topic,
+where <prefix>
is provided in the client constructor.
It publishes its alive-ness as a 1
to <prefix>/alive
and sets a will to publish 0
there when
+it is disconnected.
The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to +connect to it when errors occur.
+The client only supports paths up to MAX_TOPIC_LENGTH = 128
byte length.
+Re-publication timeout is fixed to REPUBLISH_TIMEOUT_SECONDS = 2
seconds.
use miniconf::{MqttClient, Tree};
+
+#[derive(Tree, Clone, Default)]
+struct Settings {
+ foo: bool,
+}
+
+let mut buffer = [0u8; 1024];
+let localhost: minimq::embedded_nal::IpAddr = "127.0.0.1".parse().unwrap();
+let mut client: MqttClient<'_, _, _, _, minimq::broker::IpBroker, 1> = MqttClient::new(
+ std_embedded_nal::Stack::default(),
+ "quartiq/application/12345", // prefix
+ std_embedded_time::StandardClock::default(),
+ Settings::default(),
+ minimq::ConfigBuilder::new(localhost.into(), &mut buffer).keepalive_interval(60),
+)
+.unwrap();
+
+client
+ .handled_update(|path, old_settings, new_settings| {
+ if new_settings.foo {
+ return Err("Foo!");
+ }
+ *old_settings = new_settings.clone();
+ Ok(())
+ })
+ .unwrap();
Construct a new MQTT settings interface.
+stack
- The network stack to use for communication.prefix
- The MQTT device prefix to use for this device.clock
- The clock for managing the MQTT connection.settings
- The initial settings values.config
- The configuration of the MQTT client.Update the MQTT interface and service the network. Pass any settings changes to the handler +supplied.
+handler
- A closure called with updated settings that can be used to apply current
+settings or validate the configuration. Arguments are (path, old_settings, new_settings).True if the settings changed. False otherwise.
+Update the settings from the network stack without any specific handling.
+True if the settings changed. False otherwise
+Force republication of the current settings.
+This is intended to be used if modification of a setting had side effects that affected +another setting.
+pub struct PathIter<'a, M: ?Sized, const Y: usize, P> { /* private fields */ }
An iterator over the paths in a TreeKey
.
iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)pub struct SliceShort;
Unit struct to indicate a short indices iterator in TreeKey::indices()
.
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub trait JsonCoreSlash<'de, const Y: usize = 1>: TreeSerialize<Y> + TreeDeserialize<'de, Y> {
+ // Required methods
+ fn set_json(
+ &mut self,
+ path: &str,
+ data: &'de [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn get_json(
+ &self,
+ path: &str,
+ data: &mut [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8]
+ ) -> Result<usize, Error<Error>>;
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8]
+ ) -> Result<usize, Error<Error>>;
+}
Miniconf with “JSON and /
”.
Access items with '/'
as path separator and JSON (from serde-json-core
)
+as serialization/deserialization payload format.
pub trait Key {
+ // Required method
+ fn find<const Y: usize, M: TreeKey<Y>>(&self) -> Option<usize>;
+}
Capability to convert a key into a node index for a given M: TreeKey
.
pub trait TreeDeserialize<'de, const Y: usize = 1>: TreeKey<Y> {
+ // Required method
+ fn deserialize_by_key<K, D>(
+ &mut self,
+ keys: K,
+ de: D
+ ) -> Result<usize, Error<D::Error>>
+ where K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>;
+}
Deserialize a leaf node by its keys.
+See also crate::JsonCoreSlash
for a convenient blanket implementation using this trait.
Deserialize an node by keys.
+ +#[derive(TreeKey, TreeDeserialize)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut s = S { foo: 9, bar: 11 };
+let mut de = serde_json_core::de::Deserializer::new(b"7");
+s.deserialize_by_key(["bar"].into_iter(), &mut de).unwrap();
+de.end().unwrap();
+assert_eq!(s.bar, 7);
keys
: An Iterator
of Key
s identifying the node.de
: A Deserializer
to deserialize the value.Node depth on success
+pub trait TreeKey<const Y: usize = 1> {
+ // Required methods
+ fn name_to_index(name: &str) -> Option<usize>;
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>;
+ fn metadata() -> Metadata;
+
+ // Provided methods
+ fn path<K, P>(keys: K, path: P, sep: &str) -> Result<usize, Error<Error>>
+ where K: IntoIterator,
+ K::Item: Key,
+ P: Write { ... }
+ fn indices<'a, K, I>(
+ keys: K,
+ indices: I
+ ) -> Result<usize, Error<SliceShort>>
+ where K: IntoIterator,
+ K::Item: Key,
+ I: IntoIterator<Item = &'a mut usize> { ... }
+ fn iter_paths<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> ⓘ { ... }
+ fn iter_paths_unchecked<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> ⓘ { ... }
+}
Traversal, iteration, and serialization/deserialization of nodes in a tree.
+The following documentation sections on TreeKey<Y>
apply analogously to TreeSerialize<Y>
+and TreeDeserialize<Y>
.
The TreeKey
trait (and the TreeSerialize
/TreeDeserialize
traits as well)
+are meant to be implemented
+recursively on nested data structures. Recursion here means that a container
+that implements TreeKey
, may call on the TreeKey
implementations of
+inner types.
The const parameter Y
in the traits here is the recursion depth and determines the
+maximum nesting of TreeKey
layers. It’s at least 1
and defaults to 1
.
The recursion depth Y
doubles as an upper bound to the key length
+(the depth/height of the tree):
+An implementor of TreeKey<Y>
may consume at most Y
items from the
+keys
iterator argument in the recursive methods (TreeSerialize::serialize_by_key()
,
+TreeDeserialize::deserialize_by_key()
, TreeKey::traverse_by_key()
). This includes
+both the items consumed directly before recursing and those consumed indirectly
+by recursing into inner types. In the same way it may call func
in
+TreeKey::traverse_by_key()
at most Y
times, again including those calls due
+to recursion into inner Miniconf
types.
This implies that if an implementor T
of TreeKey<Y>
(with Y >= 1
) contains and recurses into
+an inner type using that type’s TreeKey<Z>
implementation, then 1 <= Z <= Y
must
+hold and T
may consume at most Y - Z
items from the keys
iterators and call
+func
at most Y - Z
times.
The keys used to locate nodes can be either iterators over usize
or iterators
+over &str
names.
usize
may appear like ASN.1 Object Identifiers.
+&str
keys are sequences of names, like path names. When concatenated, they are separated by
+path hierarchy separators, e.g. '/'
.
Derive macros to automatically implement the correct TreeKey<Y>
traits on a struct S
are available through
+crate::TreeKey
, crate::TreeSerialize
, and crate::TreeDeserialize
.
+A shorthand derive macro that derives all three trait implementations is also available at crate::Tree
.
To derive TreeSerialize
/TreeDeserialize
, each field in the struct must either implement
+serde::Serialize
/serde::de::DeserializeOwned
+(and ultimately also be supported by the intended serde::Serializer
/serde::Deserializer
backend)
+or implement the respective TreeSerialize
/TreeDeserialize
trait themselves for the required remaining
+recursion depth.
For each field, the remaining recursion depth is configured through the #[tree(depth(Y))]
+attribute, with Y = 1
being the implied default when using #[tree()]
and Y = 0
invalid.
+If the attribute is not present, the field is a leaf and accessed only through its
+serde::Serialize
/serde::Deserialize
implementation.
+With the attribute present the field is accessed through its TreeKey<Y>
implementation with the given
+remaining recursion depth.
Blanket implementations of the TreeKey
traits are provided for homogeneous arrays [T; N]
+up to recursion depth Y = 8
.
When a [T; N]
is used as TreeKey<Y>
(i.e. marked as #[tree(depth(Y))]
in a struct)
+and Y > 1
each item of the array is accessed as a TreeKey
tree.
+For a depth Y = 0
(attribute absent), the entire array is accessed as one atomic
+value. For Y = 1
each index of the array is is instead accessed as
+one atomic value.
The type to use depends on the desired semantics of the data contained in the array. If the array
+contains TreeKey
items, one can (and often wants to) use Y >= 2
.
+However, if each element in the array should be individually configurable as a single value (e.g. a list
+of u32
), then Y = 1
can be used. With Y = 0
all items are to be accessed simultaneously and atomically.
+For e.g. [[T; 2]; 3] where T: TreeKey<3>
the recursion depth is Y = 5
. It automatically implements
+TreeKey<5>
.
+For [[T; 2]; 3] where T: Serialize + DeserializeOwned
, any Y <= 2
is available.
Blanket implementations of the TreeKey
traits are provided for Option<T>
+up to recursion depth Y = 8
.
These implementation do not alter the path hierarchy and do not consume any items from the keys
+iterators. The TreeKey
behavior of an Option
is such that the None
variant makes the corresponding part
+of the tree inaccessible at run-time. It will still be iterated over by TreeKey::iter_paths()
but attempts
+to TreeSerialize::serialize_by_key()
or TreeDeserialize::deserialize_by_key()
them
+return Error::Absent
.
+This is intended as a mechanism to provide run-time construction of the namespace. In some
+cases, run-time detection may indicate that some component is not present. In this case,
+namespaces will not be exposed for it.
If the depth specified by the #[tree(depth(Y))]
attribute exceeds 1,
+the Option
can be used to access within the inner type using its TreeKey
trait.
+If there is no tree
attribute on an Option
field in a struct or in an array, JSON
nullcorresponds to
Noneas usual and the
TreeKey` trait is not used.
The following example shows potential usage of arrays and Option
:
#[derive(TreeKey)]
+struct S {
+ // "/b/1/2" = 5
+ #[tree(depth(2))]
+ b: [[u32; 3]; 3],
+ // "/c/0" = [3,4], optionally absent at runtime
+ #[tree(depth(2))]
+ c: [Option<[u32; 2]>; 2],
+}
The macros add bounds to generic types of the struct they are acting on.
+If a generic type parameter T
of the struct S<T>
is used as a type parameter to a
+field type a: F1<F2<T>>
the type T
will be considered to reside at type depth X = 2
(as it is
+within F2
which is within F1
) and the following bounds will be applied:
#[tree()]
attribute not present on a
, T
will receive bounds Serialize
/DeserializeOwned
when
+TreeSerialize
/TreeDeserialize
is derived.#[tree(depth(Y))]
, and Y - X < 1
it will also receive bounds Serialize + DeserializeOwned
.Y - X >= 1
it will receive the bound T: TreeKey<Y - X>
.E.g. In the following T
resides at depth 2
and T: TreeKey<1>
will be inferred:
#[derive(TreeKey)]
+struct S<T> {
+ #[tree(depth(3))]
+ a: [Option<T>; 2],
+};
+// This works as [u32; N] implements TreeKey<1>:
+S::<[u32; 5]>::metadata();
+// This does not compile as u32 does not implement TreeKey<1>:
+// S::<u32>::metadata();
This behavior is upheld by and compatible with all implementations in this crate. It is only violated
+when deriving TreeKey
for a struct that (a) forwards its own type parameters as type
+parameters to its field types, (b) uses TreeKey
on those fields, and (c) those field
+types use their type parameters at other levels than TreeKey<Y - 1>
. See the
+test_derive_macro_bound_failure
test in tests/generics.rs
.
See the crate
documentation for an example showing how the traits and the derive macros work.
Convert a node name to a node index.
+The details of the mapping and the usize
index values
+are an implementation detail and only need to be stable for at runtime.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+}
+assert_eq!(S::name_to_index("bar"), Some(1));
Call a function for each node on the path described by keys.
+Traversal is aborted once func
returns an Err(E)
.
May not exhaust keys
if a leaf is found early. i.e. keys
+may be longer than required.
+If Self
is a leaf, nothing will be consumed from keys
+and Ok(0)
will be returned.
+If Self
is non-leaf (internal node) and the iterator is
+exhausted (empty),
+Err(Error::TooShort(0))
will be returned.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+assert_eq!(
+ S::traverse_by_key(["bar"].into_iter(), |index, name| {
+ assert_eq!((1, "bar"), (index, name));
+ Ok::<_, ()>(())
+ }),
+ Ok(1)
+);
keys
: An Iterator
of Key
s identifying the node.func
: A FnMut
to be called for each node on the path. Its arguments are
+the index and the name of the node at the given depth. Returning Err()
aborts
+the traversal.Final node depth on success
+Convert keys to path.
+This is typically called through a PathIter returned by TreeKey::iter_paths.
+keys
may be longer than required. Extra items are ignored.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut s = String::new();
+S::path([1], &mut s, "/").unwrap();
+assert_eq!(s, "/bar");
keys
: An Iterator
of Key
s identifying the node.path
: A string to write the separators and node names into.
+See also TreeKey::metadata() for upper bounds on path length.sep
: The path hierarchy separator to be inserted before each name.Final node depth on success
+Convert keys to indices
.
This determines the indices
of the item specified by keys
.
See also TreeKey::path()
for the analogous function.
Entries in indices
at and beyond the depth
returned are unaffected.
#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let mut i = [0; 2];
+let depth = S::indices(["bar"], &mut i).unwrap();
+assert_eq!(&i[..depth], &[1]);
keys
: An Iterator
of Key
s identifying the node.indices
: An iterator of &mut usize
to write the node indices into.
+If indices
is shorter than the node depth, Error<SliceShort>
is returned
+See also TreeKey::metadata() for upper bounds on depth.Final node depth on success
+Create an iterator of all possible paths.
+This is a depth-first walk. +The iterator will walk all paths, including those that may be absent at +run-time (see Option). +The iterator has an exact and trusted Iterator::size_hint.
+ +#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let paths: Vec<String> = S::iter_paths("/").map(|p| p.unwrap()).collect();
+assert_eq!(paths, ["/foo", "/bar"]);
P
- The type to hold the path. Needs to be core::fmt::Write + Default
sep
- The path hierarchy separatorAn iterator of paths with a trusted and exact Iterator::size_hint()
.
Create an unchecked iterator of all possible paths.
+See also TreeKey::iter_paths.
+ +#[derive(TreeKey)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let paths: Vec<String> = S::iter_paths_unchecked("/").map(|p| p.unwrap()).collect();
+assert_eq!(paths, ["/foo", "/bar"]);
P
- The type to hold the path. Needs to be core::fmt::Write + Default
.sep
- The path hierarchy separatorA iterator of paths.
+pub trait TreeSerialize<const Y: usize = 1>: TreeKey<Y> {
+ // Required method
+ fn serialize_by_key<K, S>(
+ &self,
+ keys: K,
+ ser: S
+ ) -> Result<usize, Error<S::Error>>
+ where K: Iterator,
+ K::Item: Key,
+ S: Serializer;
+}
Serialize a leaf node by its keys.
+See also crate::JsonCoreSlash
for a convenient blanket implementation using this trait.
Serialize a node by keys.
+ +#[derive(TreeKey, TreeSerialize)]
+struct S {
+ foo: u32,
+ bar: u16,
+};
+let s = S { foo: 9, bar: 11 };
+let mut buf = [0u8; 10];
+let mut ser = serde_json_core::ser::Serializer::new(&mut buf);
+s.serialize_by_key(["bar"].into_iter(), &mut ser).unwrap();
+let length = ser.end();
+assert_eq!(&buf[..length], b"11");
keys
: An Iterator
of Key
s identifying the node.ser
: A Serializer
to to serialize the value.Node depth on success.
+Redirecting to ../../miniconf/enum.Error.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/struct.Metadata.html b/firmware/miniconf/tree/struct.Metadata.html new file mode 100644 index 0000000000..d39ad1a910 --- /dev/null +++ b/firmware/miniconf/tree/struct.Metadata.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.Metadata.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/struct.SliceShort.html b/firmware/miniconf/tree/struct.SliceShort.html new file mode 100644 index 0000000000..e31490873b --- /dev/null +++ b/firmware/miniconf/tree/struct.SliceShort.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/struct.SliceShort.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.Increment.html b/firmware/miniconf/tree/trait.Increment.html new file mode 100644 index 0000000000..caecb5af9c --- /dev/null +++ b/firmware/miniconf/tree/trait.Increment.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.Increment.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.Key.html b/firmware/miniconf/tree/trait.Key.html new file mode 100644 index 0000000000..721b4becd7 --- /dev/null +++ b/firmware/miniconf/tree/trait.Key.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.Key.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeDeserialize.html b/firmware/miniconf/tree/trait.TreeDeserialize.html new file mode 100644 index 0000000000..6ff6d41bfe --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeDeserialize.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeDeserialize.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeKey.html b/firmware/miniconf/tree/trait.TreeKey.html new file mode 100644 index 0000000000..296a024a5d --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeKey.html...
+ + + \ No newline at end of file diff --git a/firmware/miniconf/tree/trait.TreeSerialize.html b/firmware/miniconf/tree/trait.TreeSerialize.html new file mode 100644 index 0000000000..557af020a0 --- /dev/null +++ b/firmware/miniconf/tree/trait.TreeSerialize.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../miniconf/trait.TreeSerialize.html...
+ + + \ No newline at end of file diff --git a/firmware/search-index.js b/firmware/search-index.js new file mode 100644 index 0000000000..3205548a1f --- /dev/null +++ b/firmware/search-index.js @@ -0,0 +1,10 @@ +var searchIndex = JSON.parse('{\ +"ad9959":{"doc":"","t":"NSDNNNNNNNNNNNNNNNNNNNNDNEQNSNNNNINNESNDNENNSSNLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK","n":["ACR","ALL","Ad9959","Bounds","CFR","CFTW0","CPOW0","CSR","CW1","CW10","CW11","CW12","CW13","CW14","CW15","CW2","CW3","CW4","CW5","CW6","CW7","CW8","CW9","Channel","Check","Error","Error","FDW","FOUR","FR1","FR2","FourBitSerial","Frequency","Interface","Interface","LSRR","Mode","ONE","Pin","ProfileSerializer","RDW","Register","SingleBitThreeWire","SingleBitTwoWire","THREE","TWO","TwoBitSerial","all","bitand","bitand_assign","bitor","bitor_assign","bits","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","complement","configure_mode","contains","difference","empty","eq","extend","finalize","fmt","fmt","fmt","fmt","fmt","freeze","from","from","from","from","from","from","from_bits","from_bits_retain","from_bits_retain","from_bits_truncate","from_iter","from_name","get_amplitude","get_frequency","get_phase","get_reference_clock_frequency","get_reference_clock_multiplier","insert","intersection","intersects","into","into","into","into","into","into","into_iter","is_all","is_empty","iter","iter_names","new","new","not","read","remove","self_test","set","set_amplitude","set_frequency","set_phase","sub","sub_assign","symmetric_difference","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","union","update_channels","write"],"q":[[0,"ad9959"],[149,"core::result"],[150,"core::iter::traits::collect"],[151,"core::fmt"],[152,"core::fmt"],[153,"bitflags::iter"],[154,"bitflags::iter"],[155,"embedded_hal::blocking::delay"],[156,"core::any"]],"d":["","","A device driver for the AD9959 direct digital synthesis …","","","","","","","","","","","","","","","","","","","","","Specifies an output channel of the AD9959 DDS chip.","","Possible errors generated by the AD9959 driver.","","","","","","","","A trait that allows a HAL to provide a means of …","","","Indicates various communication modes of the DDS. The …","","","Represents a means of serializing a DDS profile for …","","The configuration registers within the AD9959 DDS device. …","","","","","","Get a flags value with all known bits set.","The bitwise and (&
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","Get the underlying bits value.","","The bitwise exclusive-or (^
) of the bits in two flags …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","The bitwise negation (!
) of the bits in a flags value, …","","Whether all set bits in a source flags value are also set …","The intersection of a source flags value with the …","Get a flags value with all bits unset.","","The bitwise or (|
) of the bits in each flags value.","Get the serialized profile as a slice of 32-bit words.","","","","","","Finalize DDS configuration","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from a bits value.","Convert from a bits value exactly.","","Convert from a bits value, unsetting any unknown bits.","The bitwise or (|
) of the bits in each flags value.","Get a flags value with the bits of a flag with the given …","Get the configured amplitude of a channel.","Get the frequency of a channel.","Get the current phase of a specified channel.","Get the current reference clock frequency in Hz.","Get the current reference clock multiplier.","The bitwise or (|
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","Whether any set bits in a source flags value are also set …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Whether all known bits in this flags value are set.","Whether all bits in this flags value are unset.","Yield a set of contained flags values.","Yield a set of contained named flags values.","Construct and initialize the DDS.","Construct a new serializer.","The bitwise negation (!
) of the bits in a flags value, …","","The intersection of a source flags value with the …","Perform a self-test of the communication interface.","Call insert
when value
is true
or remove
when value
is …","Configure the amplitude of a specified channel.","Configure the frequency of a specified channel.","Configure the phase of a specified channel.","The intersection of a source flags value with the …","The intersection of a source flags value with the …","The bitwise exclusive-or (^
) of the bits in two flags …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","","","","","","The bitwise or (|
) of the bits in two flags values.","Update a number of channels with the requested profile.",""],"i":[25,1,0,13,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,0,13,0,15,25,1,25,25,4,13,0,13,25,0,1,13,0,25,0,4,4,1,1,4,1,1,1,1,1,1,1,1,1,14,25,8,4,1,13,14,25,8,4,1,13,4,1,15,1,1,1,4,1,8,1,1,1,1,13,14,14,25,8,4,1,13,1,1,1,1,1,1,14,14,14,14,14,1,1,1,14,25,8,4,1,13,1,1,1,1,1,14,8,1,15,1,14,1,14,14,14,1,1,1,1,14,25,8,4,1,13,14,25,8,4,1,13,14,25,8,4,1,13,1,8,15],"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],2],[[1,1],1],[[1,1],2],[1,3],[1,3],[[1,1],1],[[1,1],2],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[4,4],[1,1],[[-1,4],[[5,[2]]],[]],[[1,1],6],[[1,1],1],[[],1],[[4,4],6],[[1,-1],2,7],[8,[[10,[9]]]],[[1,11],12],[[1,11],12],[[1,11],12],[[1,11],12],[[13,11],12],[[[14,[-1]]],[[2,[-1,4]]],15],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[3,[[16,[1]]]],[3,1],[3,1],[3,1],[-1,1,7],[17,[[16,[1]]]],[[[14,[-1]],1],[[5,[18,13]]],15],[[[14,[-1]],1],[[5,[18,13]]],15],[[[14,[-1]],1],[[5,[18,13]]],15],[[[14,[-1]]],18,15],[[[14,[-1]]],[[5,[3,13]]],15],[[1,1],2],[[1,1],1],[[1,1],6],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[1],[1,6],[1,6],[1,[[19,[1]]]],[1,[[20,[1]]]],[[-1,-2,-3,-4,4,18,3],[[5,[[14,[-1]],13]]],15,21,21,[[22,[3]]]],[4,8],[1,1],[[-1,3,[10,[3]]],[[5,[2]]],[]],[[1,1],2],[[[14,[-1]]],[[5,[6,13]]],15],[[1,1,6],2],[[[14,[-1]],1,18],[[5,[18,13]]],15],[[[14,[-1]],1,18],[[5,[18,13]]],15],[[[14,[-1]],1,18],[[5,[18,13]]],15],[[1,1],1],[[1,1],2],[[1,1],1],[[1,1],2],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,23,[]],[-1,23,[]],[-1,23,[]],[-1,23,[]],[-1,23,[]],[-1,23,[]],[[1,1],1],[[8,1,[16,[9]],[16,[24]],[16,[9]]],2],[[-1,3,[10,[3]]],[[5,[2]]],[]]],"c":[],"p":[[3,"Channel",0],[15,"tuple"],[15,"u8"],[4,"Mode",0],[4,"Result",149],[15,"bool"],[8,"IntoIterator",150],[3,"ProfileSerializer",0],[15,"u32"],[15,"slice"],[3,"Formatter",151],[6,"Result",151],[4,"Error",0],[3,"Ad9959",0],[8,"Interface",0],[4,"Option",152],[15,"str"],[15,"f32"],[3,"Iter",153],[3,"IterNames",153],[8,"OutputPin",154],[8,"DelayUs",155],[3,"TypeId",156],[15,"u16"],[4,"Register",0]],"b":[[52,"impl-Channel"],[53,"impl-Flags-for-Channel"],[77,"impl-UpperHex-for-Channel"],[78,"impl-LowerHex-for-Channel"],[79,"impl-Octal-for-Channel"],[80,"impl-Binary-for-Channel"],[90,"impl-Channel"],[91,"impl-Flags-for-Channel"]]},\
+"dual_iir":{"doc":"Dual IIR","t":"RRRRRRDSSMMALLLLLLMLLLMLLLLLLMMMLLLLFFFDGFFFFDFCDDDDFFFDDDDDDDDDFFFDDDDDDFFFDDDFFFDDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLLLLLLLLMMAFMMAFLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMAMMMMMAFMMMMMMAFMMMMMMAMMMAFAFAFAFAFMAFMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAFMMMMMDDDCMMFCCDDMMMDDMMMACFDDDMMMMMMMMMMDDDCMMMMMMFCCDDDDDDCMMFCCDDDCMMMMMFCCMDDDCMMFCCMM","n":["BATCH_SIZE","IIR_CASCADE_LENGTH","SAMPLE_PERIOD","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","SCALE","Settings","__MINICONF_DEFERS","__MINICONF_NAMES","afe","allow_hold","app","borrow","borrow_mut","clone","default","deserialize_by_key","fmt","force_hold","from","get_json","get_json_by_index","iir_ch","into","metadata","name_to_index","serialize_by_key","set_json","set_json_by_index","signal_generator","stream_target","telemetry_period","traverse_by_key","try_from","try_into","type_id","DCMI","DMA1_STR4","ETH","Local","Monotonic","SPI2","SPI3","SPI4","SPI5","Shared","SysTick","_","__rtic_internal_Monotonics","__rtic_internal_eth_Context","__rtic_internal_ethernet_linkSharedResources","__rtic_internal_ethernet_link_Context","__rtic_internal_ethernet_link_Monotonic_spawn_after","__rtic_internal_ethernet_link_Monotonic_spawn_at","__rtic_internal_ethernet_link_spawn","__rtic_internal_idleSharedResources","__rtic_internal_idle_Context","__rtic_internal_init_Context","__rtic_internal_processLocalResources","__rtic_internal_processSharedResources","__rtic_internal_process_Context","__rtic_internal_settings_updateLocalResources","__rtic_internal_settings_updateSharedResources","__rtic_internal_settings_update_Context","__rtic_internal_settings_update_Monotonic_spawn_after","__rtic_internal_settings_update_Monotonic_spawn_at","__rtic_internal_settings_update_spawn","__rtic_internal_spi2_Context","__rtic_internal_spi3_Context","__rtic_internal_spi4_Context","__rtic_internal_spi5_Context","__rtic_internal_startLocalResources","__rtic_internal_start_Context","__rtic_internal_start_Monotonic_spawn_after","__rtic_internal_start_Monotonic_spawn_at","__rtic_internal_start_spawn","__rtic_internal_telemetryLocalResources","__rtic_internal_telemetrySharedResources","__rtic_internal_telemetry_Context","__rtic_internal_telemetry_Monotonic_spawn_after","__rtic_internal_telemetry_Monotonic_spawn_at","__rtic_internal_telemetry_spawn","__rtic_internal_usbLocalResources","__rtic_internal_usbSharedResources","__rtic_internal_usb_Context","__rtic_internal_usb_Monotonic_spawn_after","__rtic_internal_usb_Monotonic_spawn_at","__rtic_internal_usb_spawn","adcs","adcs","afes","afes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cnt","core","cpu_temp_sensor","cpu_temp_sensor","cs","dacs","dacs","device","digital_inputs","digital_inputs","eth","eth","ethernet_link","ethernet_link","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","generator","generator","idle","idle","iir_state","iir_state","init","init","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","local","local","local","local","local","monotonics","network","network","network","network","network","process","process","sampling_timer","sampling_timer","settings","settings","settings","settings","settings_update","settings_update","shared","shared","shared","shared","shared","shared","shared_resources","signal_generator","signal_generator","signal_generator","spi2","spi2","spi3","spi3","spi4","spi4","spi5","spi5","start","start","systick","telemetry","telemetry","telemetry","telemetry","telemetry","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","usb","usb","usb","usb","usb","usb_terminal","usb_terminal","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","usb","Context","Monotonics","core","cs","device","Monotonic","now","now","Context","LocalResources","SharedResources","adcs","dacs","digital_inputs","generator","iir_state","local","settings","shared","signal_generator","telemetry","Context","LocalResources","SharedResources","SpawnHandle","afes","local","network","settings","shared","signal_generator","spawn","spawn_after","spawn_at","Context","Context","Context","Context","Context","LocalResources","SpawnHandle","local","sampling_timer","spawn","spawn_after","spawn_at","Context","LocalResources","SharedResources","SpawnHandle","cpu_temp_sensor","local","network","settings","shared","spawn","spawn_after","spawn_at","telemetry","Context","LocalResources","SharedResources","SpawnHandle","local","shared","spawn","spawn_after","spawn_at","usb","usb_terminal"],"q":[[0,"dual_iir"],[36,"dual_iir::app"],[357,"dual_iir::app::eth"],[358,"dual_iir::app::ethernet_link"],[366,"dual_iir::app::idle"],[371,"dual_iir::app::init"],[376,"dual_iir::app::monotonics"],[378,"dual_iir::app::monotonics::Monotonic"],[379,"dual_iir::app::process"],[392,"dual_iir::app::settings_update"],[405,"dual_iir::app::spi2"],[406,"dual_iir::app::spi3"],[407,"dual_iir::app::spi4"],[408,"dual_iir::app::spi5"],[409,"dual_iir::app::start"],[417,"dual_iir::app::telemetry"],[430,"dual_iir::app::usb"],[441,"miniconf::tree"],[442,"core::result"],[443,"core::iter::traits::iterator"],[444,"serde::de"],[445,"core::fmt"],[446,"core::fmt"],[447,"miniconf::tree"],[448,"serde::ser"],[449,"serde_json_core::de"],[450,"core::ops::function"],[451,"core::any"]],"d":["","","","","","","","","","Configure the Analog Front End (AFE) gain.","Specified true if DI1 should be used as a “hold” input.","The RTIC application module","","","","","","","Specified true if “hold” should be forced regardless …","Returns the argument unchanged.","","","Configure the IIR filter parameters.","Calls U::from(self)
.","","","","","","Specifies the config for signal generators to add on to …","Specifies the target for data livestreaming.","Specifies the telemetry output period in seconds.","","","","","Interrupt handler to dispatch tasks at priority 1","User HW task ISR trampoline for process","User HW task ISR trampoline for eth","RTIC local resource struct","User code from within the module","User HW task ISR trampoline for spi2","User HW task ISR trampoline for spi3","User HW task ISR trampoline for spi4","User HW task ISR trampoline for spi5","RTIC shared resource struct","","","Monotonics used by the system","Execution context","Shared resources ethernet_link
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources idle
has access to","Execution context","Execution context","Local resources process
has access to","Shared resources process
has access to","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Execution context","Execution context","Execution context","Execution context","Local resources start
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources telemetry
has access to","Shared resources telemetry
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources usb
has access to","Shared resources usb
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","","Local resource adcs
","","Local resource afes
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Core (Cortex-M) peripherals","","Local resource cpu_temp_sensor
","Critical section token for init","","Local resource dacs
","Device peripherals","","Local resource digital_inputs
","Hardware task","User HW task: eth","Software task","User SW task ethernet_link","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Local resource generator
","Idle loop","User provided idle function","","Local resource iir_state
","Initialization function","User code end User provided init function","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","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 Resources this task has access to","Holds static methods for each monotonic.","","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Hardware task","User HW task: process","","Local resource sampling_timer
","","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Software task","User SW task settings_update","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","","","Resource proxy resource signal_generator
. Use method …","Resource proxy resource signal_generator
. Use method …","Hardware task","User HW task: spi2","Hardware task","User HW task: spi3","Hardware task","User HW task: spi4","Hardware task","User HW task: spi5","Software task","User SW task start","","Software task","User SW task telemetry","","Resource proxy resource telemetry
. Use method .lock()
to …","Resource proxy resource telemetry
. Use method .lock()
to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Software task","User SW task usb","","Resource proxy resource usb
. Use method .lock()
to gain …","Resource proxy resource usb
. Use method .lock()
to gain …","","Local resource usb_terminal
","Execution context","Execution context","Shared resources ethernet_link
has access to","","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Shared resources idle
has access to","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource usb
. Use method .lock()
to gain …","Execution context","Monotonics used by the system","Core (Cortex-M) peripherals","Critical section token for init","Device peripherals","This module holds the static implementation for …","","Read the current time from this monotonic","Execution context","Local resources process
has access to","Shared resources process
has access to","Local resource adcs
","Local resource dacs
","Local resource digital_inputs
","Local resource generator
","Local resource iir_state
","Local Resources this task has access to","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource signal_generator
. Use method …","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","","Local resource afes
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource signal_generator
. Use method …","Spawns the task directly","","","Execution context","Execution context","Execution context","Execution context","Execution context","Local resources start
has access to","","Local Resources this task has access to","Local resource sampling_timer
","Spawns the task directly","","","Execution context","Local resources telemetry
has access to","Shared resources telemetry
has access to","","Local resource cpu_temp_sensor
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Local resources usb
has access to","Shared resources usb
has access to","","Local Resources this task has access to","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource usb
. Use method .lock()
to gain …","Local resource usb_terminal
"],"i":[0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,37,26,38,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,48,24,26,43,24,26,37,24,26,37,0,0,0,0,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,26,37,0,0,26,37,0,0,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,28,34,29,35,36,0,25,39,42,44,47,0,0,26,41,25,40,42,44,0,0,22,28,29,35,36,21,0,25,40,42,0,0,0,0,0,0,0,0,0,0,48,0,0,25,40,44,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,25,26,27,24,39,22,37,40,28,20,30,31,32,33,41,34,38,42,29,43,44,35,45,46,36,47,21,0,0,25,39,46,26,45,0,0,0,0,47,21,0,0,0,0,0,39,22,39,0,0,24,24,24,0,0,0,0,0,0,37,37,37,37,37,28,40,28,40,40,0,0,0,0,38,29,42,42,29,42,0,0,0,0,0,0,0,0,0,0,34,41,0,0,0,0,0,0,0,43,35,44,44,35,0,0,0,44,0,0,0,0,36,36,0,0,0,46,45],"f":[0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[1,1],[[],1],[[1,-1,-2],[[4,[2,3]]],5,6],[[1,7],8],0,[-1,-1,[]],[[-1,9,[11,[10]]],[[4,[2,[3,[12]]]]],[]],[[-1,[11,[2]],[11,[10]]],[[4,[2,[3,[12]]]]],[]],0,[-1,-2,[],[]],[[],13],[9,[[14,[2]]]],[[1,-1,-2],[[4,[2,3]]],5,15],[[-1,9,[11,[10]]],[[4,[2,[3,[16]]]]],[]],[[-1,[11,[2]],[11,[10]]],[[4,[2,[3,[16]]]]],[]],0,0,0,[[-1,-2],[[4,[2,[3,[-3]]]]],5,17,[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,18,[]],[[],19],[[],19],[[],19],0,0,[[],19],[[],19],[[],19],[[],19],0,[[],19],0,0,0,0,0,[[],[[4,[0,19]]]],[[],[[4,[0,19]]]],[[],[[4,[19,19]]]],0,0,0,0,0,0,0,0,0,[[],[[4,[0,19]]]],[[],[[4,[0,19]]]],[[],[[4,[19,19]]]],0,0,0,0,0,0,[[],[[4,[0,19]]]],[[],[[4,[0,19]]]],[[],[[4,[19,19]]]],0,0,0,[[],[[4,[0,19]]]],[[],[[4,[0,19]]]],[[],[[4,[19,19]]]],0,0,0,[[],[[4,[0,19]]]],[[],[[4,[0,19]]]],[[],[[4,[19,19]]]],0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,0,[20,19],0,[21,19],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],0,0,0,[22,23],0,0,0,[24,[[19,[25,26,27]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,0,0,[28,19],0,0,0,0,0,0,0,[29,19],0,0,0,0,0,0,0,0,0,0,0,[30,19],0,[31,19],0,[32,19],0,[33,19],0,[34,19],0,0,[35,19],0,0,0,[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],[-1,18,[]],0,[36,19],0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[19,19]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[19,19]]]],0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[19,19]]]],0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[19,19]]]],0,0,0,0,0,0,0,0,0,[[],[[4,[19,19]]]],0,0,0,0],"c":[],"p":[[3,"Settings",0],[15,"usize"],[4,"Error",441],[4,"Result",442],[8,"Iterator",443],[8,"Deserializer",444],[3,"Formatter",445],[6,"Result",445],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error",446],[3,"Metadata",441],[4,"Option",447],[8,"Serializer",448],[4,"Error",449],[8,"FnMut",450],[3,"TypeId",451],[15,"tuple"],[3,"__rtic_internal_eth_Context",36],[3,"__rtic_internal_ethernet_link_Context",36],[3,"__rtic_internal_idle_Context",36],[15,"never"],[3,"__rtic_internal_init_Context",36],[3,"Shared",36],[3,"Local",36],[3,"__rtic_internal_Monotonics",36],[3,"__rtic_internal_process_Context",36],[3,"__rtic_internal_settings_update_Context",36],[3,"__rtic_internal_spi2_Context",36],[3,"__rtic_internal_spi3_Context",36],[3,"__rtic_internal_spi4_Context",36],[3,"__rtic_internal_spi5_Context",36],[3,"__rtic_internal_start_Context",36],[3,"__rtic_internal_telemetry_Context",36],[3,"__rtic_internal_usb_Context",36],[3,"__rtic_internal_processLocalResources",36],[3,"__rtic_internal_settings_updateLocalResources",36],[3,"__rtic_internal_idleSharedResources",36],[3,"__rtic_internal_processSharedResources",36],[3,"__rtic_internal_startLocalResources",36],[3,"__rtic_internal_settings_updateSharedResources",36],[3,"__rtic_internal_telemetryLocalResources",36],[3,"__rtic_internal_telemetrySharedResources",36],[3,"__rtic_internal_usbLocalResources",36],[3,"__rtic_internal_usbSharedResources",36],[3,"__rtic_internal_ethernet_linkSharedResources",36],[6,"Monotonic",36]],"b":[]},\
+"idsp":{"doc":"Embedded DSP algorithms","t":"QDDIDIQIDDGGSSISDSDDDDSKLLLLLLLLLLLLLKLLFLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLKLLLLLALAMLLLLLLLLLLLLLLLLLLLLLKLKLLLLLLLLLLLLLLLLKKLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKMLLLLLLLLLLLLKLFKLLLLLKLLLLLLLLLLLLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLIRRRRDDDDQDKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLKLLLLLLLLLLLLLLLLLLLLLEDDSSNNNNNNDELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDDLLLLMLLLLLLLMLLMLLLLLLLLLLL","n":["ACCU","Accu","Cascade","Coefficient","Complex","ComplexExt","Config","Filter","Lockin","Lowpass","Lowpass1","Lowpass2","MAX","MIN","MulScaled","NEG_ONE","Nyquist","ONE","PLL","RPLL","Repeat","Unwrapper","ZERO","abs_sqr","abs_sqr","add","add","add","add","add","add","add","add","add_assign","add_assign","add_assign","add_assign","arg","arg","as_","atan2","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clip","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","conj","cossin","default","default","default","default","default","default","default","default","default","default","deserialize","deserialize","deserialize","div","div","div","div","div","div","div","div","div_assign","div_assign","div_assign","div_assign","div_scaled","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","frequency","frequency","from","from","from","from","from","from","from","from","from","from","from","from","from","from_angle","from_angle","from_f32","from_f64","from_i128","from_i16","from_i32","from_i64","from_i8","from_isize","from_str","from_str_radix","from_u128","from_u16","from_u32","from_u64","from_u8","from_usize","get","get","get","get","get","hash","hbf","i","iir","im","into","into","into","into","into","into","into","into","into","into","into_iter","inv","inv","inv","is_finite","is_infinite","is_nan","is_normal","is_one","is_zero","l1_norm","log2","log2","macc","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","mul_scaled","neg","neg","new","new","new","next","norm_sqr","one","overflowing_sub","phase","phase","phase","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","pow","powi","powu","product","product","quantize","re","rem","rem","rem","rem","rem","rem","rem","rem","rem_assign","rem_assign","rem_assign","rem_assign","saturating_add","saturating_add","saturating_scale","saturating_sub","saturating_sub","scale","serialize","serialize","serialize","set","set","set","set","set","set_one","set_zero","sub","sub","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sum","sum","svf","to_f32","to_f64","to_i128","to_i16","to_i32","to_i64","to_i8","to_isize","to_u128","to_u16","to_u32","to_u64","to_u8","to_usize","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unscale","update","update","update","update","update","update","update","update","update","update_iq","wraps","y","zero","Filter","HBF_CASCADE_BLOCK","HBF_PASSBAND","HBF_TAPS","HBF_TAPS_98","HbfDec","HbfDecCascade","HbfInt","HbfIntCascade","Item","SymFir","block_size","block_size","block_size","block_size","block_size","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buf_mut","buf_mut","clone","clone","clone","clone","clone","default","default","depth","depth","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","get","into","into","into","into","into","keep_state","new","new","new","process_block","process_block","process_block","process_block","process_block","response_length","response_length","response_length","response_length","response_length","set_depth","set_depth","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","Action","Biquad","Filter","HOLD","IDENTITY","Kd","Kdd","Ki","Kii","Kp","OrderRange","Pid","PidError","allpass","angular_critical_frequency","ba","ba_mut","bandpass","bandwidth","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","clone","clone","clone","clone","clone","cmp","cmp","critical_frequency","default","default","default","deserialize","deserialize","deserialize","deserialize","deserialize","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","forward_gain","frequency","from","from","from","from","from","from","from","gain","gain","gain_db","highpass","highshelf","iho","input_offset","into","into","into","into","into","inverse_q","limit","lowpass","lowshelf","max","min","notch","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","peaking","period","proportional","q","serialize","serialize","serialize","serialize","serialize","set_input_offset","set_max","set_min","set_u","shelf","shelf_db","shelf_slope","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","u","update","State","Svf","borrow","borrow","borrow_mut","borrow_mut","bp","br","clone","deserialize","eq","fmt","from","from","hp","into","into","lp","partial_cmp","serialize","set_frequency","set_q","try_from","try_from","try_into","try_into","type_id","type_id","update"],"q":[[0,"idsp"],[342,"idsp::hbf"],[426,"idsp::iir"],[548,"idsp::svf"],[577,"core::clone"],[578,"num_traits"],[579,"num_traits"],[580,"num_traits::cast"],[581,"core::ops::arith"],[582,"core::default"],[583,"core::result"],[584,"serde::de"],[585,"serde::de"],[586,"core::fmt"],[587,"core::fmt"],[588,"num_traits::cast"],[589,"core::hash"],[590,"core::hash"],[591,"num_traits::sign"],[592,"num_traits::ops::mul_add"],[593,"num_traits::ops::mul_add"],[594,"num_traits::ops::wrapping"],[595,"core::iter::traits::iterator"],[596,"num_traits::float"],[597,"serde::ser"],[598,"core::ops::bit"],[599,"core::ops::bit"],[600,"num_traits::float"]],"d":["Accumulator type","Wrapping Accumulator","Combine two different filters in cascade","Helper trait unifying fixed point and floating point …","A complex number in Cartesian form.","Complex extension trait offering DSP (fast, good accuracy) …","Filter configuration type.","Single inpout single output i32 filter","Lockin filter","Arbitrary order, high dynamic range, wide coefficient …","First order lowpass","Second order lowpass","Highest value","Lowest value","Full scale fixed point multiplication.","Negative multiplicative identity, equal to -Self::ONE
.","Nyquist zero","Multiplicative identity","Type-II, sampled phase, discrete time PLL","Reciprocal PLL.","Repeat another filter","Overflow unwrapper.","Additive identity","Square of magnitude","Return the absolute square (the squared magnitude).","","","","","","","","","","","","","Angle","Return the angle.","","2-argument arctangent function.","","","","","","","","","","","","","","","","","","","","","Clamp to between min and max","","","","","","","","","","","Returns the complex conjugate. i.e. re - i im
","Compute the cosine and sine of an angle. This is ported …","","","","","","","","","","","","","","","","","","","","","","","","","","Division (scaled)","","","","","","","","","","","","Return the current frequency estimate","Return the current frequency estimate","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Unit magnitude from angle","Return a Complex on the unit circle given an angle.","","","","","","","","","Parses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of …","Parses a +/- bi
; ai +/- b
; a
; or bi
where a
and b
are of …","","","","","","","Return the current filter output","","","","","","Half-band filters and cascades","Returns imaginary unit","IIR filters, coefficients and applications","Imaginary portion of the complex number","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Returns 1/self
","","Checks if the given complex number is finite","Checks if the given complex number is infinite","Checks if the given complex number is NaN","Checks if the given complex number is normal","","","Returns the L1 norm |re| + |im|
– the Manhattan distance …","Log2 approximation","log2(power) re full scale approximation","Proper scaling and potentially using a wide accumulator. …","","","","","","","","","","","","","","","","","Scaled multiplication for fixed point","Multiplication (scaled)","","","","","","Create a new accumulator with given initial state and step.","Create a new Complex","Create a new RPLL instance.","","Returns the square of the norm (since T
doesn’t …","","Subtract y - x
with signed overflow.","Return the current phase estimate","Return the current phase estimate","The current phase","","","","","","","","","","","","","","","","","","","","","","","","","Raises self
to a signed integer power.","Raises self
to an unsigned integer power.","","","Scale and quantize a floating point value.","Real portion of the complex number","","","","","","","","","","","","","Staturating addition","","Combine high and low i32 into a single downscaled i32, …","Saturating subtraction","","Multiplies self
by the scalar t
.","","","","Update the filter so that it outputs the provided value. …","","","","","","","","","","","","","","","","","","","","","State variable filter","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Divides self
by the scalar t
.","Update the filter with a new sample.","","","","Update the lockin with a sample taken at a given phase.","","Update the PLL with a new phase sample. This needs to be …","Advance the RPLL and optionally supply a new timestamp.","Feed a new sample..","Update the lockin with a sample taken at a local …","The current number of wraps","Current output including wraps","","Filter input items into output items.","Max low-rate block size (HbfIntCascade input, …","Passband width in units of lowest sample rate","140 dB stopband, 2 µdB passband ripple, limited by f32 …","Standard/optimal half-band filter cascade taps","Half band decimator (decimate by two)","Half-band decimation filter cascade with optimal taps","Half band interpolator (interpolation rate 2)","Half-band interpolation filter cascade with optimal taps.","Input/output item type.","Symmetric FIR filter prototype.","Return the block size granularity and the maximum block …","","","","","","","","","","","","","","","Obtain a mutable reference to the input items buffer space.","Obtain a mutable reference to the input items buffer space","","","","","","","","Cascade depth","Cascade depth","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Perform the FIR convolution and yield results iteratively.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Move items as new filter state.","Create a new SymFir
.","Create a new HbfDec
.","Non-zero (odd) taps from oldest to one-before-center. …","Process a block of items.","","","","","Finite impulse response length in numer of output items …","","","","","Set cascade depth","Set cascade depth","","","","","","","","","","","","","","","","PID action","Biquad IIR filter","Standard audio biquad filter builder","A “hold” filter that ingests input and maintains output","A unity gain filter","Derivative=, 20 dB per decade","Double derivative, 40 dB per decade","Integrating, -20 dB per decade","Double integrating, -40 dB per decade","Proportional","The action gains cover more than three successive orders","PID controller builder","Pid::build()
errors","An allpass filter","Set relative critical angular frequency","Filter coefficients","Mutable reference to the filter coefficients.","Band pass","Set the relative bandwidth","","","","","","","","","","","Perform checks, compute coefficients and return Biquad
.","","","","","","","","Set relative critical frequency","","","","","","","","","","","","","","","","","","","Compute the overall (DC/proportional feed-forward) gain.","Set crititcal frequency from absolute units.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Set reference gain","Gain for a given action","Set reference gain in dB","High pass filter","Low shelf","I/HO","Compute input-referred (x
) offset.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Set inverse Q parameter of the filter","Gain limit for a given action","Low pass filter","Low shelf","Upper output limit","Lower output limit","A notch filter","","","","","","A peaking/dip filter","Sample period","A filter with the given proportional gain at all …","Set Q parameter of the filter","","","","","","Convert input (x
) offset to equivalent summing junction …","Set the upper output limit","Set the lower output limit","Set the summing junction offset","Set linear shelf gain","Set shelf gain in dB","Set the shelf slope.","","","","","","","","","","","","","","","","Summing junction offset","Direct Form 1 Update","Second order state variable filter state","State variable filter","","","","","Bandpass output","Bandreject (notch) output","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Highpass output","Calls U::from(self)
.","Calls U::from(self)
.","Lowpass output","","","Set the critical frequency","Set the Q parameter","","","","","","","Update the filter"],"i":[86,0,0,0,0,0,56,0,0,0,0,0,86,86,0,86,0,86,0,0,0,0,86,93,2,2,2,2,2,2,2,2,2,2,2,2,2,93,2,2,0,10,2,11,12,13,14,15,16,17,18,10,2,11,12,13,14,15,16,17,18,86,10,2,11,12,13,14,15,16,17,18,2,0,10,2,11,12,13,14,15,16,17,18,2,16,18,2,2,2,2,2,2,2,2,2,2,2,2,86,10,2,10,2,2,2,2,2,2,2,2,16,17,10,2,2,2,2,11,12,13,14,15,16,17,18,93,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,56,11,12,13,15,2,0,2,0,2,10,2,11,12,13,14,15,16,17,18,10,2,2,2,2,2,2,2,2,2,2,93,2,86,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,94,86,2,2,2,2,2,10,2,17,10,2,2,0,16,17,18,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,86,2,2,2,2,2,2,2,2,2,2,2,2,2,93,2,0,93,2,2,2,16,18,56,11,12,13,15,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,10,2,11,12,13,14,15,16,17,18,10,2,11,12,13,14,15,16,17,18,10,2,11,12,13,14,15,16,17,18,2,56,11,12,13,14,15,16,17,18,14,18,18,2,0,0,0,0,0,0,0,0,0,95,0,95,73,77,78,79,80,73,77,78,79,80,73,77,78,79,80,77,80,73,77,78,79,78,79,78,79,80,73,77,78,79,80,73,77,78,79,80,80,73,77,78,79,80,80,73,77,95,73,77,78,79,95,73,77,78,79,78,79,80,73,77,78,79,80,73,77,78,79,80,73,77,78,79,0,0,0,85,85,89,89,89,89,89,88,0,0,83,83,85,85,83,83,85,83,87,88,89,85,83,87,88,89,87,85,83,87,88,89,88,89,83,85,83,87,85,83,87,88,89,85,83,87,88,89,85,83,87,88,89,85,83,85,85,85,83,87,88,89,83,87,83,83,83,83,85,85,83,87,88,89,83,87,83,83,85,85,83,85,83,87,88,89,83,87,85,83,85,83,87,88,89,85,85,85,85,83,83,83,85,83,87,88,89,85,83,87,88,89,85,83,87,88,89,85,85,0,0,91,92,91,92,91,91,92,92,92,92,91,92,91,91,92,91,92,92,92,92,91,92,91,92,91,92,92],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[[[2,[1]]],3],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[-1,-2,[],[]],[[[2,[1]]],1],[[[2,[-2]]],-1,8,[[9,[-1]]]],[[1,1],1],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[-1,-1,-1],-1,[]],[[[10,[-1]]],[[10,[-1]]],4],[[[2,[-1]]],[[2,[-1]]],4],[11,11],[[[12,[-1]]],[[12,[-1]]],4],[[[13,[-1,-2]]],[[13,[-1,-2]]],4,4],[[[14,[-1]]],[[14,[-1]]],4],[15,15],[16,16],[17,17],[[[18,[-1]]],[[18,[-1]]],4],[[[2,[-1]]],[[2,[-1]]],[4,5,19]],[1,[[6,[1,1]]]],[[],[[10,[-1]]],20],[[],[[2,[-1]]],20],[[],11],[[],[[12,[-1]]],[20,8]],[[],[[13,[-1,-2]]],20,20],[[],[[14,[-1]]],20],[[],15],[[],16],[[],17],[[],[[18,[-1]]],20],[-1,[[21,[[2,[-2]]]]],22,[23,5,4]],[-1,[[21,[16]]],22],[-1,[[21,[[18,[-2]]]]],22,23],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[-1,-1],-1,[]],[[[10,[-1]],[10,[-1]]],24,25],[[[2,[-1]],[2,[-1]]],24,25],[[[10,[-1]],26],27,28],[[[2,[-1]],26],[[21,[6,29]]],[30,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],28],[[[2,[-1]],26],[[21,[6,29]]],[32,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],[33,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],[34,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],[35,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],[36,5,31,4]],[[[2,[-1]],26],[[21,[6,29]]],[37,5,31,4]],[16,1],[17,3],[-1,-1,[]],[-1,[[2,[-1]]],[4,5]],[-1,[[2,[-1]]],[4,5]],[-1,[[38,[[2,[-2]]]]],39,[40,5]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[1,[[2,[1]]]],[41,[[38,[[2,[-1]]]]],[42,5]],[43,[[38,[[2,[-1]]]]],[42,5]],[44,[[38,[[2,[-1]]]]],[42,5]],[45,[[38,[[2,[-1]]]]],[42,5]],[1,[[38,[[2,[-1]]]]],[42,5]],[46,[[38,[[2,[-1]]]]],[42,5]],[47,[[38,[[2,[-1]]]]],[42,5]],[48,[[38,[[2,[-1]]]]],[42,5]],[49,[[21,[[2,[-1]]]]],[50,5,4]],[[49,3],[[21,[[2,[-1]]]]],[5,4]],[51,[[38,[[2,[-1]]]]],[42,5]],[52,[[38,[[2,[-1]]]]],[42,5]],[3,[[38,[[2,[-1]]]]],[42,5]],[53,[[38,[[2,[-1]]]]],[42,5]],[54,[[38,[[2,[-1]]]]],[42,5]],[55,[[38,[[2,[-1]]]]],[42,5]],[-1,1,[]],[11,1],[[[12,[-1]]],1,56],[[[13,[-1,-2]]],1,56,56],[15,1],[[[2,[-1]],-2],6,57,58],0,[[],[[2,[-1]]],[4,5]],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[2,[-1]]],[],[4,5,19]],[[[2,[-1]]],[[2,[-1]]],[4,5,19]],[[[2,[-1]]],[],[4,5,19]],[[[2,[-1]]],24,59],[[[2,[-1]]],24,59],[[[2,[-1]]],24,59],[[[2,[-1]]],24,59],[[[2,[-1]]],24,[4,5]],[[[2,[-1]]],24,[4,5]],[[[2,[-1]]],-1,[4,60]],[-1,-2,[],[]],[[[2,[1]]],1],[[-1,-1,-1,-1],[[6,[-1,-1]]],[]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]],[2,[-1]]],[[2,[-1]]],[4,5,61]],[[[2,[-1]],[2,[-1]],[2,[-1]]],[[2,[-1]]],[4,5,61]],[[[2,[-1]],[2,[-1]],[2,[-1]]],6,[4,7,62]],[[[2,[-1]],[2,[-1]],[2,[-1]]],6,[4,7,62]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[-1,-2],-1,[],[]],[[-1,-1],-1,[]],[[[2,[1]],1],[[2,[1]]]],[[[2,[1]],[2,[1]]],[[2,[1]]]],[[[2,[1]],45],[[2,[1]]]],[[[2,[-1]]],[],[4,5,19]],[[[2,[-1]]],[],[4,5,19]],[[-1,-1],[[10,[-1]]],[]],[[-1,-1],[[2,[-1]]],[]],[3,17],[[[10,[-1]]],[[38,[-1]]],[63,8]],[[[2,[-1]]],-1,[4,5]],[[],[[2,[-1]]],[4,5]],[[-1,-1],[[6,[-1,1]]],[64,65,31]],[16,1],[17,1],[[[18,[-2]]],-1,8,[[9,[-1]],63,8]],[[[2,[-1]],54],[],[4,5]],[[[2,[-1]],48],[],[4,5,19]],[[[2,[-1]],45],[],[4,5,19]],[[[2,[-1]],54],[],[4,5]],[[[2,[-1]],47],[],[4,5,19]],[[[2,[-1]],53],[],[4,5]],[[[2,[-1]],47],[],[4,5,19]],[[[2,[-1]],46],[],[4,5,19]],[[[2,[-1]],52],[],[4,5]],[[[2,[-1]],46],[],[4,5,19]],[[[2,[-1]],55],[],[4,5]],[[[2,[-1]],51],[],[4,5]],[[[2,[-1]],1],[],[4,5,19]],[[[2,[-1]],55],[],[4,5]],[[[2,[-1]],52],[],[4,5]],[[[2,[-1]],48],[],[4,5,19]],[[[2,[-1]],1],[],[4,5,19]],[[[2,[-1]],51],[],[4,5]],[[[2,[-1]],53],[],[4,5]],[[[2,[-1]],44],[],[4,5,19]],[[[2,[-1]],3],[],[4,5]],[[[2,[-1]],45],[],[4,5,19]],[[[2,[-1]],3],[],[4,5]],[[[2,[-1]],44],[],[4,5,19]],[[[2,[-1]],1],[[2,[-1]]],[4,5,19]],[[[2,[-1]],3],[[2,[-1]]],[4,5]],[-1,[[2,[-2]]],66,[5,4]],[-1,[[2,[-2]]],66,[5,4]],[-1,-2,[67,9],[[9,[-1]]]],0,[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[-1,-1],-1,[]],[[[2,[1]],[2,[1]]],[[2,[1]]]],[[1,1,3],1],[[-1,-1],-1,[]],[[[2,[1]],[2,[1]]],[[2,[1]]]],[[[2,[-1]],-1],[[2,[-1]]],[4,5]],[[[2,[-1]],-2],21,68,69],[[16,-1],21,69],[[[18,[-1]],-2],21,68,69],[[-1,1],6,[]],[[11,1],6],[[[12,[-1]],1],6,56],[[[13,[-1,-2]],1],6,56,56],[[15,1],6],[[[2,[-1]]],6,[4,5]],[[[2,[-1]]],6,[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],[2,[-1]]],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],-1],[],[4,5]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[[[2,[-1]],[2,[-1]]],6,[4,7]],[[[2,[-1]],-1],6,[4,7]],[-1,[[2,[-2]]],66,[5,4]],[-1,[[2,[-2]]],66,[5,4]],0,[[[2,[-1]]],[[38,[41]]],[39,5]],[[[2,[-1]]],[[38,[43]]],[39,5]],[[[2,[-1]]],[[38,[44]]],[39,5]],[[[2,[-1]]],[[38,[45]]],[39,5]],[[[2,[-1]]],[[38,[1]]],[39,5]],[[[2,[-1]]],[[38,[46]]],[39,5]],[[[2,[-1]]],[[38,[47]]],[39,5]],[[[2,[-1]]],[[38,[48]]],[39,5]],[[[2,[-1]]],[[38,[51]]],[39,5]],[[[2,[-1]]],[[38,[52]]],[39,5]],[[[2,[-1]]],[[38,[3]]],[39,5]],[[[2,[-1]]],[[38,[53]]],[39,5]],[[[2,[-1]]],[[38,[54]]],[39,5]],[[[2,[-1]]],[[38,[55]]],[39,5]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[[[2,[-1]],-1],[[2,[-1]]],[4,5]],[[-1,1],1,[]],[[11,1],1],[[[12,[-1]],1],1,56],[[[13,[-1,-2]],1],1,56,56],[[[14,[-1]],1,1],[[2,[1]]],56],[[15,1],1],[[16,[38,[1]],1],6],[[17,[38,[1]],3,3],[[6,[1,3]]]],[[[18,[-1]],-2],-2,[],[64,8,[9,[-1]]]],[[[14,[-1]],1,[2,[1]]],[[2,[1]]],56],[[[18,[-2]]],-1,[8,63,60,[71,[3]]],[[9,[-1]],[72,[3]],63,8]],[[[18,[-1]]],-1,[63,8]],[[],[[2,[-1]]],[4,5]],0,0,0,0,0,0,0,0,0,0,0,[-1,[[6,[55,55]]],[]],[[[73,[-1]]],[[6,[55,55]]],[8,65,74,75,76,0]],[[[77,[-1]]],[[6,[55,55]]],[8,65,74,75,76]],[78,[[6,[55,55]]]],[79,[[6,[55,55]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[80,[-1]]],[[81,[-1]]],[8,65,74,75,76]],[[[77,[-1]]],[[81,[-1]]],[8,65,74,75,76]],[[[80,[-1]]],[[80,[-1]]],4],[[[73,[-1]]],[[73,[-1]]],4],[[[77,[-1]]],[[77,[-1]]],4],[78,78],[79,79],[[],78],[[],79],[78,55],[79,55],[[[80,[-1]],26],27,28],[[[73,[-1]],26],27,28],[[[77,[-1]],26],27,28],[[78,26],27],[[79,26],27],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[[80,[-1]]],[[0,[66]]],[8,65,74,75,76]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[80,[-1]],55],6,[8,65,74,75,76]],[[[82,[-1]]],[[80,[-1]]],[8,65,74,75,76]],[[[82,[-1]]],[[73,[-1]]],[65,8,74,75,76]],[[[82,[-1]]],[[77,[-1]]],[8,65,74,75,76]],[[-1,[38,[81]],81],81,[]],[[[73,[-1]],[38,[81]],81],81,[8,65,74,75,76,0]],[[[77,[-1]],[38,[81]],81],81,[8,65,74,75,76]],[[78,[38,[81]],81],81],[[79,[38,[81]],81],81],[-1,55,[]],[[[73,[-1]]],55,[8,65,74,75,76,0]],[[[77,[-1]]],55,[8,65,74,75,76]],[78,55],[79,55],[[78,55],6],[[79,55],6],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],0,0,0,0,0,0,0,0,0,0,0,0,0,[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[85,[-1]]],[[82,[-1]]],86],[[[85,[-1]]],[[82,[-1]]],86],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[87,[-1]]],[[21,[[82,[-2]],88]]],[],[86,[9,[-1]]]],[[[85,[-1]]],[[85,[-1]]],4],[[[83,[-1]]],[[83,[-1]]],4],[[[87,[-1]]],[[87,[-1]]],4],[88,88],[89,89],[[88,88],90],[[89,89],90],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[],[[85,[-1]]],86],[[],[[83,[-1]]],[67,84]],[[],[[87,[-1]]],67],[-1,[[21,[[85,[-2]]]]],22,23],[-1,[[21,[[83,[-2]]]]],22,23],[-1,[[21,[[87,[-2]]]]],22,23],[-1,[[21,[88]]],22],[-1,[[21,[89]]],22],[[[85,[-1]],[85,[-1]]],24,25],[[[83,[-1]],[83,[-1]]],24,25],[[[87,[-1]],[87,[-1]]],24,25],[[88,88],24],[[89,89],24],[[[85,[-1]],26],27,28],[[[83,[-1]],26],27,28],[[[87,[-1]],26],27,28],[[88,26],27],[[89,26],27],[[[85,[-1]]],-1,86],[[[83,[-1]],-1,-1],[[83,[-1]]],[67,84]],[[[82,[-1]]],[[85,[-1]]],86],[-1,-1,[]],[[[82,[-1]]],[[85,[-2]]],[],[86,[9,[-1]]]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[87,[-1]],89,-1],[[87,[-1]]],67],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[85,[-1]]],-1,86],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[87,[-1]],89,-1],[[87,[-1]]],67],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[85,[-1]]],-1,86],[[[85,[-1]]],-1,86],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[85,[-1]],[85,[-1]]],[[38,[90]]],31],[[[83,[-1]],[83,[-1]]],[[38,[90]]],31],[[[87,[-1]],[87,[-1]]],[[38,[90]]],31],[[88,88],[[38,[90]]]],[[89,89],[[38,[90]]]],[[[83,[-1]]],[[82,[-1]]],[67,84]],[[[87,[-1]],-1],[[87,[-1]]],67],[-1,[[85,[-1]]],86],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[85,[-1]],-2],21,68,69],[[[83,[-1]],-2],21,68,69],[[[87,[-1]],-2],21,68,69],[[88,-1],21,69],[[89,-1],21,69],[[[85,[-1]],-1],6,86],[[[85,[-1]],-1],6,86],[[[85,[-1]],-1],6,86],[[[85,[-1]],-1],6,86],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[[[83,[-1]],-1],[[83,[-1]]],[67,84]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[-1,70,[]],[[[85,[-1]]],-1,86],[[[85,[-1]],[82,[-1]],-1],-1,86],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[[[91,[-1]]],-1,67],[[[92,[-1]]],[[92,[-1]]],4],[-1,[[21,[[92,[-2]]]]],22,23],[[[92,[-1]],[92,[-1]]],24,25],[[[92,[-1]],26],27,28],[-1,-1,[]],[-1,-1,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],0,[[[92,[-1]],[92,[-1]]],[[38,[90]]],31],[[[92,[-1]],-2],21,68,69],[[[92,[-1]],-1],6,[67,84]],[[[92,[-1]],-1],6,[67,84]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,[[21,[-2]]],[],[]],[-1,70,[]],[-1,70,[]],[[[92,[-1]],[91,[-1]],-1],6,[67,84]]],"c":[],"p":[[15,"i32"],[3,"Complex",0],[15,"u32"],[8,"Clone",577],[8,"Num",578],[15,"tuple"],[8,"NumAssign",578],[8,"Copy",579],[8,"AsPrimitive",580],[3,"Accu",0],[3,"Nyquist",0],[3,"Repeat",0],[3,"Cascade",0],[3,"Lockin",0],[3,"Lowpass",0],[3,"PLL",0],[3,"RPLL",0],[3,"Unwrapper",0],[8,"Neg",581],[8,"Default",582],[4,"Result",583],[8,"Deserializer",584],[8,"Deserialize",584],[15,"bool"],[8,"PartialEq",585],[3,"Formatter",586],[6,"Result",586],[8,"Debug",586],[3,"Error",586],[8,"Octal",586],[8,"PartialOrd",585],[8,"UpperExp",586],[8,"Binary",586],[8,"Display",586],[8,"LowerHex",586],[8,"LowerExp",586],[8,"UpperHex",586],[4,"Option",587],[8,"ToPrimitive",580],[8,"NumCast",580],[15,"f32"],[8,"FromPrimitive",580],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"str"],[8,"FromStr",588],[15,"u128"],[15,"u16"],[15,"u64"],[15,"u8"],[15,"usize"],[8,"Filter",0],[8,"Hash",589],[8,"Hasher",589],[8,"FloatCore",590],[8,"Signed",591],[8,"MulAdd",592],[8,"MulAddAssign",592],[8,"WrappingAdd",593],[8,"WrappingSub",593],[8,"Zero",594],[8,"Iterator",595],[8,"Float",590],[8,"Serialize",596],[8,"Serializer",596],[3,"TypeId",597],[8,"BitAnd",598],[8,"Shr",598],[3,"HbfDec",342],[8,"Add",581],[8,"Mul",581],[8,"Sum",599],[3,"HbfInt",342],[3,"HbfDecCascade",342],[3,"HbfIntCascade",342],[3,"SymFir",342],[15,"slice"],[15,"array"],[3,"Filter",426],[8,"FloatConst",590],[3,"Biquad",426],[8,"Coefficient",0],[3,"Pid",426],[4,"PidError",426],[4,"Action",426],[4,"Ordering",585],[3,"State",548],[3,"Svf",548],[8,"ComplexExt",0],[8,"MulScaled",0],[8,"Filter",342]],"b":[[25,"impl-Add%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[26,"impl-Add%3C%26T%3E-for-Complex%3CT%3E"],[27,"impl-Add%3C%26T%3E-for-%26Complex%3CT%3E"],[28,"impl-Add%3CComplex%3CT%3E%3E-for-%26Complex%3CT%3E"],[29,"impl-Add%3CT%3E-for-%26Complex%3CT%3E"],[30,"impl-Add-for-Complex%3CT%3E"],[31,"impl-Add%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[32,"impl-Add%3CT%3E-for-Complex%3CT%3E"],[33,"impl-AddAssign%3C%26T%3E-for-Complex%3CT%3E"],[34,"impl-AddAssign%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[35,"impl-AddAssign-for-Complex%3CT%3E"],[36,"impl-AddAssign%3CT%3E-for-Complex%3CT%3E"],[87,"impl-Div%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[88,"impl-Div%3CT%3E-for-%26Complex%3CT%3E"],[89,"impl-Div%3C%26T%3E-for-Complex%3CT%3E"],[90,"impl-Div-for-Complex%3CT%3E"],[91,"impl-Div%3CComplex%3CT%3E%3E-for-%26Complex%3CT%3E"],[92,"impl-Div%3CT%3E-for-Complex%3CT%3E"],[93,"impl-Div%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[94,"impl-Div%3C%26T%3E-for-%26Complex%3CT%3E"],[95,"impl-DivAssign%3C%26T%3E-for-Complex%3CT%3E"],[96,"impl-DivAssign%3CT%3E-for-Complex%3CT%3E"],[97,"impl-DivAssign%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[98,"impl-DivAssign-for-Complex%3CT%3E"],[103,"impl-Octal-for-Complex%3CT%3E"],[104,"impl-Debug-for-Complex%3CT%3E"],[105,"impl-UpperExp-for-Complex%3CT%3E"],[106,"impl-Binary-for-Complex%3CT%3E"],[107,"impl-Display-for-Complex%3CT%3E"],[108,"impl-LowerHex-for-Complex%3CT%3E"],[109,"impl-LowerExp-for-Complex%3CT%3E"],[110,"impl-UpperHex-for-Complex%3CT%3E"],[114,"impl-From%3C%26T%3E-for-Complex%3CT%3E"],[115,"impl-From%3CT%3E-for-Complex%3CT%3E"],[116,"impl-NumCast-for-Complex%3CT%3E"],[165,"impl-Inv-for-%26Complex%3CT%3E"],[166,"impl-Complex%3CT%3E"],[167,"impl-Inv-for-Complex%3CT%3E"],[178,"impl-Mul%3CT%3E-for-Complex%3CT%3E"],[179,"impl-Mul%3C%26T%3E-for-%26Complex%3CT%3E"],[180,"impl-Mul%3C%26T%3E-for-Complex%3CT%3E"],[181,"impl-Mul%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[182,"impl-Mul%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[183,"impl-Mul%3CT%3E-for-%26Complex%3CT%3E"],[184,"impl-Mul%3CComplex%3CT%3E%3E-for-%26Complex%3CT%3E"],[185,"impl-Mul-for-Complex%3CT%3E"],[186,"impl-MulAdd%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[187,"impl-MulAdd-for-Complex%3CT%3E"],[188,"impl-MulAddAssign-for-Complex%3CT%3E"],[189,"impl-MulAddAssign%3C%26Complex%3CT%3E,+%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[190,"impl-MulAssign%3CT%3E-for-Complex%3CT%3E"],[191,"impl-MulAssign-for-Complex%3CT%3E"],[192,"impl-MulAssign%3C%26T%3E-for-Complex%3CT%3E"],[193,"impl-MulAssign%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[196,"impl-MulScaled%3Ci32%3E-for-Complex%3Ci32%3E"],[197,"impl-MulScaled%3CComplex%3Ci32%3E%3E-for-Complex%3Ci32%3E"],[198,"impl-MulScaled%3Ci16%3E-for-Complex%3Ci32%3E"],[199,"impl-Neg-for-%26Complex%3CT%3E"],[200,"impl-Neg-for-Complex%3CT%3E"],[211,"impl-Pow%3C%26u8%3E-for-%26Complex%3CT%3E"],[212,"impl-Pow%3C%26isize%3E-for-%26Complex%3CT%3E"],[213,"impl-Pow%3C%26i16%3E-for-%26Complex%3CT%3E"],[214,"impl-Pow%3Cu8%3E-for-%26Complex%3CT%3E"],[215,"impl-Pow%3C%26i8%3E-for-%26Complex%3CT%3E"],[216,"impl-Pow%3C%26u64%3E-for-%26Complex%3CT%3E"],[217,"impl-Pow%3Ci8%3E-for-%26Complex%3CT%3E"],[218,"impl-Pow%3Ci64%3E-for-%26Complex%3CT%3E"],[219,"impl-Pow%3Cu16%3E-for-%26Complex%3CT%3E"],[220,"impl-Pow%3C%26i64%3E-for-%26Complex%3CT%3E"],[221,"impl-Pow%3C%26usize%3E-for-%26Complex%3CT%3E"],[222,"impl-Pow%3C%26u128%3E-for-%26Complex%3CT%3E"],[223,"impl-Pow%3C%26i32%3E-for-%26Complex%3CT%3E"],[224,"impl-Pow%3Cusize%3E-for-%26Complex%3CT%3E"],[225,"impl-Pow%3C%26u16%3E-for-%26Complex%3CT%3E"],[226,"impl-Pow%3Cisize%3E-for-%26Complex%3CT%3E"],[227,"impl-Pow%3Ci32%3E-for-%26Complex%3CT%3E"],[228,"impl-Pow%3Cu128%3E-for-%26Complex%3CT%3E"],[229,"impl-Pow%3Cu64%3E-for-%26Complex%3CT%3E"],[230,"impl-Pow%3Ci128%3E-for-%26Complex%3CT%3E"],[231,"impl-Pow%3Cu32%3E-for-%26Complex%3CT%3E"],[232,"impl-Pow%3Ci16%3E-for-%26Complex%3CT%3E"],[233,"impl-Pow%3C%26u32%3E-for-%26Complex%3CT%3E"],[234,"impl-Pow%3C%26i128%3E-for-%26Complex%3CT%3E"],[237,"impl-Product%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[238,"impl-Product-for-Complex%3CT%3E"],[241,"impl-Rem-for-Complex%3CT%3E"],[242,"impl-Rem%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[243,"impl-Rem%3C%26T%3E-for-Complex%3CT%3E"],[244,"impl-Rem%3CT%3E-for-%26Complex%3CT%3E"],[245,"impl-Rem%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[246,"impl-Rem%3CT%3E-for-Complex%3CT%3E"],[247,"impl-Rem%3CComplex%3CT%3E%3E-for-%26Complex%3CT%3E"],[248,"impl-Rem%3C%26T%3E-for-%26Complex%3CT%3E"],[249,"impl-RemAssign%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[250,"impl-RemAssign%3CT%3E-for-Complex%3CT%3E"],[251,"impl-RemAssign%3C%26T%3E-for-Complex%3CT%3E"],[252,"impl-RemAssign-for-Complex%3CT%3E"],[269,"impl-Sub%3C%26Complex%3CT%3E%3E-for-%26Complex%3CT%3E"],[270,"impl-Sub%3CT%3E-for-Complex%3CT%3E"],[271,"impl-Sub%3CComplex%3CT%3E%3E-for-%26Complex%3CT%3E"],[272,"impl-Sub%3CT%3E-for-%26Complex%3CT%3E"],[273,"impl-Sub%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[274,"impl-Sub-for-Complex%3CT%3E"],[275,"impl-Sub%3C%26T%3E-for-Complex%3CT%3E"],[276,"impl-Sub%3C%26T%3E-for-%26Complex%3CT%3E"],[277,"impl-SubAssign-for-Complex%3CT%3E"],[278,"impl-SubAssign%3CT%3E-for-Complex%3CT%3E"],[279,"impl-SubAssign%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[280,"impl-SubAssign%3C%26T%3E-for-Complex%3CT%3E"],[281,"impl-Sum-for-Complex%3CT%3E"],[282,"impl-Sum%3C%26Complex%3CT%3E%3E-for-Complex%3CT%3E"],[484,"impl-From%3C%5BT;+5%5D%3E-for-Biquad%3CT%3E"],[486,"impl-From%3C%26%5BC;+6%5D%3E-for-Biquad%3CT%3E"]]},\
+"lockin":{"doc":"Lockin","t":"RRENNNENNNNNNRRDSSMALLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLMMLLLLLMMLLLLLLLLLLFFFDGDFCDDDDFFFDDDDDDDDDFFFDDFFFDDDFFFDDDFFFMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMAFAFLLLLLLLLLLLLLLLLLLLLLLLMMAFAFLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMAMMMMMMMAFMMMMMMAFMMMMMMAMMAFMAFMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAFMMMMMDDDCMMFCCDDMMMDDMMMACFDDDMMMMMMMMMMMDDDCMMMMMFCCDDCMMFCCDDDCMMMMMMFCCMDDDCMMFCCMM","n":["BATCH_SIZE","BATCH_SIZE_LOG2","Conf","External","InPhase","Internal","LockinMode","LogPower","Magnitude","Modulation","Phase","Quadrature","ReferenceFrequency","SAMPLE_TICKS","SAMPLE_TICKS_LOG2","Settings","__MINICONF_DEFERS","__MINICONF_NAMES","afe","app","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","default","deserialize","deserialize","deserialize_by_key","eq","fmt","fmt","fmt","from","from","from","get_json","get_json_by_index","into","into","into","lockin_harmonic","lockin_k","lockin_mode","lockin_phase","metadata","name_to_index","output_conf","pll_tc","serialize","serialize","serialize_by_key","set_json","set_json_by_index","stream_target","telemetry_period","traverse_by_key","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","DCMI","DMA1_STR4","ETH","Local","Monotonic","Shared","SysTick","_","__rtic_internal_Monotonics","__rtic_internal_eth_Context","__rtic_internal_ethernet_linkSharedResources","__rtic_internal_ethernet_link_Context","__rtic_internal_ethernet_link_Monotonic_spawn_after","__rtic_internal_ethernet_link_Monotonic_spawn_at","__rtic_internal_ethernet_link_spawn","__rtic_internal_idleSharedResources","__rtic_internal_idle_Context","__rtic_internal_init_Context","__rtic_internal_processLocalResources","__rtic_internal_processSharedResources","__rtic_internal_process_Context","__rtic_internal_settings_updateLocalResources","__rtic_internal_settings_updateSharedResources","__rtic_internal_settings_update_Context","__rtic_internal_settings_update_Monotonic_spawn_after","__rtic_internal_settings_update_Monotonic_spawn_at","__rtic_internal_settings_update_spawn","__rtic_internal_startLocalResources","__rtic_internal_start_Context","__rtic_internal_start_Monotonic_spawn_after","__rtic_internal_start_Monotonic_spawn_at","__rtic_internal_start_spawn","__rtic_internal_telemetryLocalResources","__rtic_internal_telemetrySharedResources","__rtic_internal_telemetry_Context","__rtic_internal_telemetry_Monotonic_spawn_after","__rtic_internal_telemetry_Monotonic_spawn_at","__rtic_internal_telemetry_spawn","__rtic_internal_usbLocalResources","__rtic_internal_usbSharedResources","__rtic_internal_usb_Context","__rtic_internal_usb_Monotonic_spawn_after","__rtic_internal_usb_Monotonic_spawn_at","__rtic_internal_usb_spawn","adcs","adcs","afes","afes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_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","cnt","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","generator","generator","idle","idle","init","init","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","local","local","local","local","local","lockin","lockin","monotonics","network","network","network","network","network","pll","pll","process","process","sampling_timer","sampling_timer","settings","settings","settings","settings","settings_update","settings_update","shared","shared","shared","shared","shared","shared","shared_resources","signal_generator","signal_generator","start","start","systick","telemetry","telemetry","telemetry","telemetry","telemetry","timestamper","timestamper","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_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","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","usb","usb","usb","usb","usb","usb_terminal","usb_terminal","Context","Context","SharedResources","SpawnHandle","network","shared","spawn","spawn_after","spawn_at","Context","SharedResources","network","shared","usb","Context","Monotonics","core","cs","device","Monotonic","now","now","Context","LocalResources","SharedResources","adcs","dacs","generator","local","lockin","pll","settings","shared","signal_generator","telemetry","timestamper","Context","LocalResources","SharedResources","SpawnHandle","afes","local","network","settings","shared","spawn","spawn_after","spawn_at","Context","LocalResources","SpawnHandle","local","sampling_timer","spawn","spawn_after","spawn_at","Context","LocalResources","SharedResources","SpawnHandle","cpu_temp_sensor","digital_inputs","local","network","settings","shared","spawn","spawn_after","spawn_at","telemetry","Context","LocalResources","SharedResources","SpawnHandle","local","shared","spawn","spawn_after","spawn_at","usb","usb_terminal"],"q":[[0,"lockin"],[70,"lockin::app"],[350,"lockin::app::eth"],[351,"lockin::app::ethernet_link"],[359,"lockin::app::idle"],[364,"lockin::app::init"],[369,"lockin::app::monotonics"],[371,"lockin::app::monotonics::Monotonic"],[372,"lockin::app::process"],[386,"lockin::app::settings_update"],[398,"lockin::app::start"],[406,"lockin::app::telemetry"],[420,"lockin::app::usb"],[431,"core::result"],[432,"serde::de"],[433,"miniconf::tree"],[434,"core::iter::traits::iterator"],[435,"core::fmt"],[436,"core::fmt"],[437,"miniconf::tree"],[438,"serde::ser"],[439,"serde_json_core::de"],[440,"core::ops::function"],[441,"core::any"]],"d":["","","","Utilize an external modulation signal supplied to DI0","Output the in-phase component of the lockin signal.","Utilize an internally generated reference for demodulation","","Output the logarithmic power of the lockin","Output the lockin magnitude.","Output the lockin internal modulation frequency as a …","Output the phase of the lockin","Output the quadrature component of the lockin signal.","Output the lockin reference frequency as a sinusoid","","","","","","Configure the Analog Front End (AFE) gain.","The RTIC application module","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Specifies which harmonic to use for the lockin.","Specifies the lockin lowpass gains.","Specifies the operational mode of the lockin.","Specifies the LO phase offset.","","","Specifies DAC output mode.","Specifis the PLL time constant.","","","","","","Specifies the target for data livestreaming.","Specifies the telemetry output period in seconds.","","","","","","","","","","","Interrupt handler to dispatch tasks at priority 1","User HW task ISR trampoline for process","User HW task ISR trampoline for eth","RTIC local resource struct","User code from within the module","RTIC shared resource struct","","","Monotonics used by the system","Execution context","Shared resources ethernet_link
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Shared resources idle
has access to","Execution context","Execution context","Local resources process
has access to","Shared resources process
has access to","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources start
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources telemetry
has access to","Shared resources telemetry
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","Local resources usb
has access to","Shared resources usb
has access to","Execution context","Spawns the task after a set duration relative to the …","Spawns the task at a fixed time instant","Spawns the task directly","","Local resource adcs
","","Local resource afes
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Core (Cortex-M) peripherals","","Local resource cpu_temp_sensor
","Critical section token for init","","Local resource dacs
","Device peripherals","","Local resource digital_inputs
","Hardware task","User HW task: eth","Software task","User SW task ethernet_link","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Local resource generator
","Idle loop","User provided idle function","Initialization function","User code end User provided init function","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","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 Resources this task has access to","","Local resource lockin
","Holds static methods for each monotonic.","","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource network
. Use method .lock()
to …","","Local resource pll
","Hardware task","User HW task: process","","Local resource sampling_timer
","","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Software task","User SW task settings_update","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","Shared Resources this task has access to","","","Local resource signal_generator
","Software task","User SW task start","","Software task","User SW task telemetry","","Resource proxy resource telemetry
. Use method .lock()
to …","Resource proxy resource telemetry
. Use method .lock()
to …","","Local resource timestamper
","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Software task","User SW task usb","","Resource proxy resource usb
. Use method .lock()
to gain …","Resource proxy resource usb
. Use method .lock()
to gain …","","Local resource usb_terminal
","Execution context","Execution context","Shared resources ethernet_link
has access to","","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Shared resources idle
has access to","Resource proxy resource network
. Use method .lock()
to …","Shared Resources this task has access to","Resource proxy resource usb
. Use method .lock()
to gain …","Execution context","Monotonics used by the system","Core (Cortex-M) peripherals","Critical section token for init","Device peripherals","This module holds the static implementation for …","","Read the current time from this monotonic","Execution context","Local resources process
has access to","Shared resources process
has access to","Local resource adcs
","Local resource dacs
","Local resource generator
","Local Resources this task has access to","Local resource lockin
","Local resource pll
","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Local resource signal_generator
","Resource proxy resource telemetry
. Use method .lock()
to …","Local resource timestamper
","Execution context","Local resources settings_update
has access to","Shared resources settings_update
has access to","","Local resource afes
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Execution context","Local resources start
has access to","","Local Resources this task has access to","Local resource sampling_timer
","Spawns the task directly","","","Execution context","Local resources telemetry
has access to","Shared resources telemetry
has access to","","Local resource cpu_temp_sensor
","Local resource digital_inputs
","Local Resources this task has access to","Resource proxy resource network
. Use method .lock()
to …","Resource proxy resource settings
. Use method .lock()
to …","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource telemetry
. Use method .lock()
to …","Execution context","Local resources usb
has access to","Shared resources usb
has access to","","Local Resources this task has access to","Shared Resources this task has access to","Spawns the task directly","","","Resource proxy resource usb
. Use method .lock()
to gain …","Local resource usb_terminal
"],"i":[0,0,0,2,1,2,0,1,1,1,1,1,1,0,0,0,3,3,3,0,1,2,3,1,2,3,1,2,3,3,1,2,3,2,1,2,3,1,2,3,3,3,1,2,3,3,3,3,3,3,3,3,3,1,2,3,3,3,3,3,3,1,2,3,1,2,3,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,36,29,37,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,47,27,29,42,27,29,36,27,29,42,0,0,0,0,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,29,36,0,0,0,0,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,31,33,32,34,35,29,36,0,28,38,41,43,46,29,36,0,0,29,40,28,39,41,43,0,0,25,31,32,34,35,24,0,29,36,0,0,47,0,0,28,39,43,29,36,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,28,29,30,27,38,25,36,39,31,23,40,33,37,41,32,42,43,34,44,45,35,46,24,0,0,28,38,45,29,44,0,0,0,0,46,24,0,0,0,0,0,38,25,38,0,0,27,27,27,0,0,0,0,0,0,36,36,36,31,36,36,39,31,36,39,36,0,0,0,0,37,32,41,41,32,0,0,0,0,0,0,33,40,0,0,0,0,0,0,0,42,42,34,43,43,34,0,0,0,43,0,0,0,0,35,35,0,0,0,45,44],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[1,1],[2,2],[3,3],[[],3],[-1,[[4,[1]]],5],[-1,[[4,[2]]],5],[[3,-1,-2],[[4,[6,7]]],8,5],[[2,2],9],[[1,10],11],[[2,10],11],[[3,10],11],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[-1,12,[14,[13]]],[[4,[6,[7,[15]]]]],[]],[[-1,[14,[6]],[14,[13]]],[[4,[6,[7,[15]]]]],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,[[],16],[12,[[17,[6]]]],0,0,[[1,-1],4,18],[[2,-1],4,18],[[3,-1,-2],[[4,[6,7]]],8,18],[[-1,12,[14,[13]]],[[4,[6,[7,[19]]]]],[]],[[-1,[14,[6]],[14,[13]]],[[4,[6,[7,[19]]]]],[]],0,0,[[-1,-2],[[4,[6,[7,[-3]]]]],8,20,[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[[],22],[[],22],[[],22],0,0,0,[[],22],0,0,0,0,0,[[],[[4,[0,22]]]],[[],[[4,[0,22]]]],[[],[[4,[22,22]]]],0,0,0,0,0,0,0,0,0,[[],[[4,[0,22]]]],[[],[[4,[0,22]]]],[[],[[4,[22,22]]]],0,0,[[],[[4,[0,22]]]],[[],[[4,[0,22]]]],[[],[[4,[22,22]]]],0,0,0,[[],[[4,[0,22]]]],[[],[[4,[0,22]]]],[[],[[4,[22,22]]]],0,0,0,[[],[[4,[0,22]]]],[[],[[4,[0,22]]]],[[],[[4,[22,22]]]],0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,0,[23,22],0,[24,22],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],0,0,0,[25,26],0,[27,[[22,[28,29,30]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[31,22],0,0,0,0,0,0,0,[32,22],0,0,0,0,0,0,0,0,0,0,[33,22],0,0,[34,22],0,0,0,0,0,[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,[[4,[-2]]],[],[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],[-1,21,[]],0,[35,22],0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[22,22]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[22,22]]]],0,0,0,0,0,0,0,[[],[[4,[22,22]]]],0,0,0,0,0,0,0,0,0,0,0,0,[[],[[4,[22,22]]]],0,0,0,0,0,0,0,0,0,[[],[[4,[22,22]]]],0,0,0,0],"c":[],"p":[[4,"Conf",0],[4,"LockinMode",0],[3,"Settings",0],[4,"Result",431],[8,"Deserializer",432],[15,"usize"],[4,"Error",433],[8,"Iterator",434],[15,"bool"],[3,"Formatter",435],[6,"Result",435],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error",436],[3,"Metadata",433],[4,"Option",437],[8,"Serializer",438],[4,"Error",439],[8,"FnMut",440],[3,"TypeId",441],[15,"tuple"],[3,"__rtic_internal_eth_Context",70],[3,"__rtic_internal_ethernet_link_Context",70],[3,"__rtic_internal_idle_Context",70],[15,"never"],[3,"__rtic_internal_init_Context",70],[3,"Shared",70],[3,"Local",70],[3,"__rtic_internal_Monotonics",70],[3,"__rtic_internal_process_Context",70],[3,"__rtic_internal_settings_update_Context",70],[3,"__rtic_internal_start_Context",70],[3,"__rtic_internal_telemetry_Context",70],[3,"__rtic_internal_usb_Context",70],[3,"__rtic_internal_processLocalResources",70],[3,"__rtic_internal_settings_updateLocalResources",70],[3,"__rtic_internal_idleSharedResources",70],[3,"__rtic_internal_processSharedResources",70],[3,"__rtic_internal_startLocalResources",70],[3,"__rtic_internal_settings_updateSharedResources",70],[3,"__rtic_internal_telemetryLocalResources",70],[3,"__rtic_internal_telemetrySharedResources",70],[3,"__rtic_internal_usbLocalResources",70],[3,"__rtic_internal_usbSharedResources",70],[3,"__rtic_internal_ethernet_linkSharedResources",70],[6,"Monotonic",70]],"b":[]},\
+"miniconf":{"doc":"Miniconf","t":"NEINIIDDNDNDNNYIYIYIYLLLLLLLLLLLLLLMLKLLLLKLLLLLLLLLLLLLKKLKLLLLLLLLLLLLMMKCKLLLLLKKKLLKLLLLLLLLLLLLLLLL","n":["Absent","Error","Increment","Inner","JsonCoreSlash","Key","Metadata","MqttClient","NotFound","PathIter","PostDeserialization","SliceShort","TooLong","TooShort","Tree","TreeDeserialize","TreeDeserialize","TreeKey","TreeKey","TreeSerialize","TreeSerialize","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","count","default","deserialize_by_key","eq","eq","eq","eq","find","fmt","fmt","fmt","fmt","fmt","force_republish","from","from","from","from","from","from","from","get_json","get_json_by_index","handled_update","increment","indices","indices","into","into","into","into","into","into_iter","iter_paths","iter_paths","iter_paths_unchecked","iter_paths_unchecked","max_depth","max_length","metadata","minimq","name_to_index","new","next","path","path","separator","serialize_by_key","set_json","set_json_by_index","settings","size_hint","traverse_by_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","update"],"q":[[0,"miniconf"],[104,"core::clone"],[105,"core::marker"],[106,"core::result"],[107,"core::iter::traits::iterator"],[108,"serde::de"],[109,"core::cmp"],[110,"core::option"],[111,"core::fmt"],[112,"core::fmt"],[113,"embedded_time::clock"],[114,"minimq::broker"],[115,"serde_json_core::ser"],[116,"minimq"],[117,"core::ops::function"],[118,"core::iter::traits::collect"],[119,"core::fmt"],[120,"minimq"],[121,"core::fmt"],[122,"serde_json_core::de"],[123,"core::any"]],"d":["The key is valid, but does not exist at runtime.","Errors that can occur when using the Tree traits.","Pass a Result
up one hierarchy level, incrementing its …","The value provided could not be serialized or deserialized …","Miniconf with “JSON and /
”.","Capability to convert a key into a node index for a given …","Metadata about a TreeKey namespace.","MQTT settings interface.","The key was not found (index unparsable or too large, name …","An iterator over the paths in a TreeKey
.","There was an error after deserializing a value.","Unit struct to indicate a short indices iterator in …","The key is too long and goes beyond a leaf node.","The key ends early and does not reach a leaf node.","Shorthand to derive the TreeKey
, TreeSerialize
, and …","Deserialize a leaf node by its keys.","Derive the TreeDeserialize
trait for a struct.","Traversal, iteration, and serialization/deserialization of …","Derive the TreeKey
trait for a struct.","Serialize a leaf node by its keys.","Derive the TreeSerialize
trait for a struct.","","","","","","","","","","","","","","","The total number of paths.","","Deserialize an node by keys.","","","","","Convert the key self
to a usize
index.","","","","","","Force republication of the current settings.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Retrieve a serialized value by path.","Retrieve a serialized value by indices.","Update the MQTT interface and service the network. Pass …","Increment the depth
member by one.","Convert keys to indices
.","Convert keys to indices
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Create an iterator of all possible paths.","Create an iterator of all possible paths.","Create an unchecked iterator of all possible paths.","Create an unchecked iterator of all possible paths.","The maximum path depth.","The maximum length of a path in bytes.","Get metadata about the paths in the namespace.","","Convert a node name to a node index.","Construct a new MQTT settings interface.","","Convert keys to path.","Convert keys to path.","Add separator length to the maximum path length.","Serialize a node by keys.","Update an element by path.","Update an element by indices.","Get the current settings from miniconf.","","Call a function for each node on the path described by …","","","","","","","","","","","","","","","","Update the settings from the network stack without any …"],"i":[1,0,0,1,0,0,0,0,1,0,1,0,1,1,0,0,0,0,0,0,0,18,1,3,4,5,18,1,3,4,5,1,3,4,5,4,4,41,1,3,4,5,42,1,1,3,4,5,18,18,1,1,1,3,4,5,20,20,18,43,35,35,18,1,3,4,5,5,35,35,35,35,4,4,35,0,35,18,5,35,35,4,44,20,20,18,5,35,18,1,3,4,5,18,1,3,4,5,18,1,3,4,5,18],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[1,[-1]]],[[1,[-1]]],2],[3,3],[4,4],[[[5,[-1,-2]]],[[5,[-1,-2]]],[2,6],2],0,[[],4],[[-1,-2,-3],[[8,[7,1]]],[],9,10],[[[1,[-1]],[1,[-1]]],11,12],[[3,3],11],[[4,4],11],[[[5,[-1,-2]],[5,[-1,-2]]],11,[12,6],12],[-1,[[13,[7]]],[]],[[[1,[-1]],14],15,16],[[[1,[-1]],14],15,17],[[3,14],15],[[4,14],15],[[[5,[-1,-2]],14],15,[16,6],16],[[[18,[-1,-2,-3,-4]]],19,[20,2],21,[22,2],23],[-1,-1,[]],[24,-1,[]],[-1,[[1,[-1]]],[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[-1,25,[27,[26]]],[[8,[7,[1,[28]]]]],[]],[[-1,[27,[7]],[27,[26]]],[[8,[7,[1,[28]]]]],[]],[[[18,[-1,-2,-3,-4]],-5],[[8,[11,29]]],[20,2],21,[22,2],23,30],[-1,-1,[]],[[-1,-2],[[8,[7,[1,[3]]]]],31,31],[[-1,-2],[[8,[7,[1,[3]]]]],31,31],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[25,[[5,[-1,-2]]],[],32],[25,[[5,[-1,-2]]],[],32],[25,[[5,[-1,-2]]],[],32],[25,[[5,[-1,-2]]],[],32],0,0,[[],4],0,[25,[[13,[7]]]],[[-1,25,-2,-3,[33,[-4]]],[[8,[[18,[-3,-1,-2,-4]],34]]],21,[22,2],[20,2],23],[[[5,[-1,-2]]],13,[35,6],[32,36]],[[-1,-2,25],[[8,[7,[1,[37]]]]],31,32],[[-1,-2,25],[[8,[7,[1,[37]]]]],31,32],[[4,25],4],[[-1,-2,-3],[[8,[7,1]]],[],9,38],[[-1,25,[27,[26]]],[[8,[7,[1,[39]]]]],[]],[[-1,[27,[7]],[27,[26]]],[[8,[7,[1,[39]]]]],[]],[[[18,[-1,-2,-3,-4]]],-1,[20,2],21,[22,2],23],[[[5,[-1,-2]]],[[19,[7,[13,[7]]]]],[35,6],[32,36]],[[-1,-2],[[8,[7,[1,[-3]]]]],9,30,[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,[[8,[-2]]],[],[]],[-1,40,[]],[-1,40,[]],[-1,40,[]],[-1,40,[]],[-1,40,[]],[[[18,[-1,-2,-3,-4]]],[[8,[11,29]]],[20,2],21,[22,2],23]],"c":[],"p":[[4,"Error",0],[8,"Clone",104],[3,"SliceShort",0],[3,"Metadata",0],[3,"PathIter",0],[8,"Sized",105],[15,"usize"],[4,"Result",106],[8,"Iterator",107],[8,"Deserializer",108],[15,"bool"],[8,"PartialEq",109],[4,"Option",110],[3,"Formatter",111],[6,"Result",111],[8,"Debug",111],[8,"Display",111],[3,"MqttClient",0],[15,"tuple"],[8,"JsonCoreSlash",0],[8,"TcpClientStack",112],[8,"Clock",113],[8,"Broker",114],[15,"never"],[15,"str"],[15,"u8"],[15,"slice"],[4,"Error",115],[4,"Error",116],[8,"FnMut",117],[8,"IntoIterator",118],[8,"Write",111],[3,"ConfigBuilder",119],[4,"ProtocolError",116],[8,"TreeKey",0],[8,"Default",120],[3,"Error",111],[8,"Serializer",121],[4,"Error",122],[3,"TypeId",123],[8,"TreeDeserialize",0],[8,"Key",0],[8,"Increment",0],[8,"TreeSerialize",0]],"b":[[43,"impl-Debug-for-Error%3CE%3E"],[44,"impl-Display-for-Error%3CE%3E"]]},\
+"stabilizer":{"doc":"","t":"AAAGGGGGGGGGEGGRGGNNNNGGGNGGAALLAAAACALLLCALAAALAAAALLLDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNEDLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLDDDSSSLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLRRRRRRRRRRRGRRDLLLLLLLLLLLLDLLLLLLLLLLDLLMMLMMLLMMMLLLLFFFNNNNNNNEDDDENENNNDNNNNNNNNNNNNDDNDNMMMMALLLLLLLLLLLLLLLLLLLLMLLLLLLLLLALLLLLMMLLLLLLLLLMLLLLLLLLLLLLALLLLLLLLLLLLMMMLLLMMMLMLMLALLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLILKKLKDDLLLLLLLLLLLLLLLLLLLEDNNLLLLLLLLLLLLLLLLLILKDLLLLLLLLLLLDDDDDDDMMMLLLLLLLLLLLLLLLLMMLMMMLLLLLLLLLLLLLLMMMMMMMMMMFMMMMMMMMLLLLLLLLLLLLLLLLLLLLLMMMDENDLLLLLLLLLLLLLLLLLLLLLLLLLLLDDNENNNEDNNNMMLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLMMMLLLLMMMLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNEDENDDEDNNNNNEENLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAAAALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEEEDDDDDDDDDNNNNNNNNNNNNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDGEDNNNENNLLLLLLLLLALLLLLLFCLLLLCMALMCAMLLLLLLLLLLLLLNDNDEDNLLLLLLLLLLLLLLLLLLLLLLLLMMLLLFLLLLLLLLLLLLDLLLLLLMLLLLDDDMMLLLLLLLMMMLMMLLLLLLLLLLLLLLLLLLLLENNDDDLLLLLLLLMLLMLLLLLLLLLLLLMMLLLLLLMLMLLMLLLLLLLLLMLMLLLLLLLLLLLLL","n":["hardware","net","settings","AFE0","AFE1","DigitalInput0","DigitalInput1","EemDigitalInput0","EemDigitalInput1","EemDigitalOutput0","EemDigitalOutput1","EthernetPhy","HardwareVersion","I2c1","I2c1Proxy","MONOTONIC_FREQUENCY","NetworkManager","NetworkStack","Rev1_0","Rev1_1","Rev1_2","Rev1_3","SerialTerminal","SystemTimer","Systick","Unknown","UsbBus","UsbDevice","adc","afe","borrow","borrow_mut","cpu_temp_sensor","dac","delay","design_parameters","embedded_hal","flash","fmt","from","from","hal","input_stamper","into","metadata","platform","pounder","serialize","setup","shared_adc","signal_generator","timers","try_from","try_into","type_id","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","Flash","borrow","borrow_mut","capacity","erase","from","into","range","read","try_from","try_into","type_id","write","InputStamper","borrow","borrow_mut","from","into","latest_timestamp","new","start","try_from","try_into","type_id","ApplicationMetadata","borrow","borrow_mut","features","firmware_version","from","git_dirty","hardware_version","into","new","panic_info","profile","rust_version","serialize","try_from","try_into","type_id","dfu_bootflag","execute_system_bootloader","start_dfu_reboot","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","dns_storage","eem_gpio","from","from","from","from","from","from","from","into","into","into","into","into","into","into","ip_addrs","lvds4","lvds5","lvds6","lvds7","mac_address","metadata","net","phy","pounder","setup","sockets","stack","systick","tcp_socket_storage","temperature_sensor","timestamp_timer","timestamper","timestamper","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","udp_socket_storage","usb","usb_serial","AdcChannel","AdcError","InUse","SharedAdc","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","create_channel","fmt","from","from","from","into","into","into","new","read_normalized","read_raw","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","BasicConfig","Config","Cosine","Error","InvalidAmplitude","InvalidFrequency","InvalidSymmetry","Signal","SignalGenerator","Square","Triangle","WhiteNoise","amplitude","amplitude","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear_phase_accumulator","clone","clone","clone","clone","default","default","deserialize","deserialize_by_key","fmt","fmt","fmt","fmt","fmt","frequency","from","from","from","from","from","get_json","get_json_by_index","into","into","into","into","into","into_iter","metadata","name_to_index","new","next","phase","phase_increment","phase_offset","serialize","serialize_by_key","set_json","set_json_by_index","signal","signal","symmetry","traverse_by_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into_config","type_id","type_id","type_id","type_id","type_id","update_waveform","Ch1Compare","Ch2Compare","Ch3Compare","Ch4Compare","ComparePulse","Disabled","Div1","Div1N1","Div1N8","Div2","Div4","Div8","Enable","InputFilter","PounderTimestampTimer","Prescaler","Reset","SamplingTimer","ShadowSamplingTimer","SlaveMode","TimestampTimer","Trigger","Trigger0","Trigger1","Trigger2","Trigger3","TriggerGenerator","TriggerSource","Update","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channels","channels","channels","channels","from","from","from","from","from","from","from","from","from","generate_trigger","generate_trigger","generate_trigger","generate_trigger","get_period","get_period","get_period","get_period","into","into","into","into","into","into","into","into","into","new","new","new","new","set_external_clock","set_external_clock","set_external_clock","set_external_clock","set_period_ticks","set_period_ticks","set_period_ticks","set_period_ticks","set_slave_mode","set_slave_mode","set_slave_mode","set_slave_mode","set_trigger_source","set_trigger_source","set_trigger_source","set_trigger_source","start","start","start","start","tim2","tim3","tim5","tim8","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from_primitive","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update_event","update_event","update_event","update_event","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","CaptureSource1","CaptureSource2","CaptureSource3","CaptureSource4","Channel1","Channel1InputCapture","Channel2","Channel2InputCapture","Channel3","Channel3InputCapture","Channel4","Channel4InputCapture","Channels","Ti1","Ti1","Ti2","Ti2","Ti3","Ti3","Ti4","Ti4","Trc","Trc","Trc","Trc","UpdateEvent","address","address","address","address","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","ch1","ch2","ch3","ch4","check_overcapture","check_overcapture","check_overcapture","check_overcapture","clone","clone","clone","clone","configure_filter","configure_filter","configure_filter","configure_filter","configure_prescaler","configure_prescaler","configure_prescaler","configure_prescaler","enable","enable","enable","enable","eq","eq","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_input_capture","into_input_capture","into_input_capture","into_input_capture","latest_capture","latest_capture","latest_capture","latest_capture","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","listen_dma","new","new","to_output_compare","to_output_compare","to_output_compare","to_output_compare","trigger","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","MqttStorage","NetworkReference","NetworkState","NetworkUsers","NoChange","NoChange","SettingsChanged","UpdateState","Updated","Updated","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","configure_streaming","data_stream","default","direct_stream","from","from","from","from","get_device_prefix","heapless","into","into","into","into","miniconf","miniconf","network_processor","new","processor","serde","telemetry","telemetry","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","update","AdcDacData","DataStream","Fls","FrameGenerator","StreamFormat","StreamTarget","Unknown","add","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","default","deserialize","eq","fmt","fmt","from","from","from","from","into","into","into","into","ip","port","process","serialize","set_remote","setup_streaming","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","NetworkProcessor","borrow","borrow_mut","from","handle_link","into","new","stack","try_from","try_into","type_id","update","Telemetry","TelemetryBuffer","TelemetryClient","adcs","adcs","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","cpu_temp","dacs","dacs","default","digital_inputs","digital_inputs","finalize","from","from","from","into","into","into","new","publish","serialize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","update","Error","Flash","Postcard","SerialSettingsPlatform","Settings","SettingsItem","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","broker","clone","cmd","data","default","deserialize","deserialize_by_key","deserialize_from","fmt","from","from","from","from","from","get_json","get_json_by_index","id","interface","interface_mut","into","into","into","into","key","mac","metadata","metadata","name_to_index","new","path","reload","reset","save","serialize","serialize_by_key","serialize_into","set_json","set_json_by_index","settings","settings","settings_mut","storage","traverse_by_key","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id"],"q":[[0,"stabilizer"],[3,"stabilizer::hardware"],[55,"stabilizer::hardware::adc"],[91,"stabilizer::hardware::afe"],[121,"stabilizer::hardware::cpu_temp_sensor"],[131,"stabilizer::hardware::dac"],[170,"stabilizer::hardware::delay"],[181,"stabilizer::hardware::design_parameters"],[195,"stabilizer::hardware::flash"],[208,"stabilizer::hardware::input_stamper"],[219,"stabilizer::hardware::metadata"],[236,"stabilizer::hardware::platform"],[239,"stabilizer::hardware::pounder"],[410,"stabilizer::hardware::pounder::attenuators"],[416,"stabilizer::hardware::pounder::dds_output"],[437,"stabilizer::hardware::pounder::hrtimer"],[458,"stabilizer::hardware::pounder::rf_power"],[461,"stabilizer::hardware::pounder::timestamp"],[473,"stabilizer::hardware::setup"],[562,"stabilizer::hardware::shared_adc"],[593,"stabilizer::hardware::signal_generator"],[677,"stabilizer::hardware::timers"],[815,"stabilizer::hardware::timers::tim2"],[959,"stabilizer::hardware::timers::tim3"],[1143,"stabilizer::hardware::timers::tim5"],[1327,"stabilizer::hardware::timers::tim8"],[1511,"stabilizer::net"],[1564,"stabilizer::net::data_stream"],[1613,"stabilizer::net::network_processor"],[1625,"stabilizer::net::telemetry"],[1663,"stabilizer::settings"],[1732,"core::fmt"],[1733,"core::fmt"],[1734,"serde::ser"],[1735,"core::any"],[1736,"core::ops::function"],[1737,"stm32h7::stm32h743v"],[1738,"stm32h7xx_hal::spi"],[1739,"stm32h7xx_hal::spi"],[1740,"stm32h7xx_hal::dma::dma"],[1741,"serde::de"],[1742,"embedded_hal::digital::v2"],[1743,"num_enum"],[1744,"stm32h7::stm32h743v"],[1745,"stm32h7::stm32h743v"],[1746,"core::ops::range"],[1747,"core::option"],[1748,"stm32h7xx_hal::gpio"],[1749,"stm32h7xx_hal::gpio::gpioa"],[1750,"ad9959"],[1751,"stm32h7xx_hal::xspi::common"],[1752,"stm32h7::stm32h743v"],[1753,"lm75"],[1754,"mcp230xx"],[1755,"mcp230xx"],[1756,"stm32h7::stm32h743v"],[1757,"stm32h7xx_hal::rcc::rec"],[1758,"stm32h7xx_hal::gpio::gpioa"],[1759,"stm32h7::stm32h743v"],[1760,"stm32h7xx_hal::adc"],[1761,"core::iter::traits::iterator"],[1762,"serde_json_core::ser"],[1763,"miniconf::tree"],[1764,"core::ops::function"],[1765,"stm32h7::stm32h743v"],[1766,"miniconf::json_core"],[1767,"core::clone"],[1768,"serde::ser"],[1769,"heapless::string"],[1770,"minimq::broker"],[1771,"minimq::mqtt_client"],[1772,"core::fmt"],[1773,"smoltcp::wire::ethernet"]],"d":["Module for all hardware-specific setup of Stabilizer","Stabilizer network management module","Stabilizer Settings Management","","","","","","","","","","","","","System timer (RTIC Monotonic) tick frequency","","","","","","","","","","","","","Stabilizer ADC management interface","","","","STM32 Temperature Sensor Driver","Stabilizer DAC management interface","Basic blocking delay","","","","","","Returns the argument unchanged.","","Digital Input 0 (DI0) reference clock timestamper","Calls U::from(self)
.","","","","","Stabilizer hardware configuration","","","The sampling timer is used for managing ADC sampling and …","","","","Represents data associated with ADC.","Represents data associated with ADC.","A type representing an ADC sample.","","","","","","","","Construct an ADC code from the stabilizer-defined code …","Construct an ADC code from a provided binary …","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 the ADC input channel.","Construct the ADC input channel.","Enable the ADC DMA transfer sequence.","Enable the ADC DMA transfer sequence.","","","","","","","","","","","Wait for the transfer of the currently active buffer to …","Wait for the transfer of the currently active buffer to …","","","","","","A programmable gain amplifier that allows for setting the …","Get the AFE gain as a numerical value.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Get the programmed gain of the analog front-end.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new programmable gain driver.","","Set the gain of the front-end.","","","","","","","","","A driver to access the CPU temeprature sensor.","","","Returns the argument unchanged.","Get the temperature of the CPU in degrees Celsius.","Calls U::from(self)
.","Construct the temperature sensor.","","","","Represents data associated with DAC.","Represents data associated with DAC.","Custom type for referencing DAC output codes. The internal …","","","","","","","","","","","Encode signed 16-bit values into DAC offset binary for a …","Create a dac code from the provided DAC output code.","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 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 …","","","","","","","Returns the argument unchanged.","Calls U::from(self)
.","","","","","","","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.","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self)
.","Construct the global metadata.","","","","","","","","Check if the DFU reboot flag is set, indicating a reboot …","Execute the DFU bootloader stored in system memory.","Indicate a reboot to DFU is requested.","","","","","","","","The numerical value (discriminant) of the Channel enum is …","","","","","","","","","","","","","","","","","","","","","","","","A structure containing implementation for Pounder hardware.","","A structure for the QSPI interface for the DDS.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Configure the operations mode of the interface.","The DdsOutput is used as an output stream to the pounder …","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","The HRTimer (High Resolution Timer) is used to generate …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Latch a configuration into a digital attenuator.","","","","Initialize the QSPI interface.","Construct and initialize pounder-specific hardware.","","","","","","","","","Reset all of the attenuators to a power-on default state.","","Sample one of the two auxiliary ADC channels associated …","Sample an ADC channel.","","","","","","Select external reference clock input.","Set the state (its electrical level) of the given GPIO pin …","","ADC sample timestamper using external Pounder reference …","Read the raw attenuation codes stored in the attenuator …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write data over QSPI to the DDS.","Provide an interface for managing digital attenuators on …","Get the attenuation of a channel.","","","Set the attenuation of a single channel.","","The DDS profile update stream.","A temporary builder for serializing and writing profiles.","","","","","Get a builder for serializing a Pounder DDS profile.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new DDS output stream.","","","","","","","Update a number of channels with the provided configuration","Write a profile to the stream.","Write the profile to the DDS asynchronously.","A HRTimer output channel.","The high resolution timer. Currently, only Timer E is …","","","","","","","Configure the timer to operate in single-shot mode.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new high resolution timer for generating …","Generate a single trigger of the timer to start the output …","","","","","","","Provide an interface to measure RF input power in dBm.","Measure the power of an input channel in dBm.","","Software unit to timestamp stabilizer ADC samples using an …","","","Returns the argument unchanged.","Calls U::from(self)
.","Obtain a timestamp.","Construct the pounder sample timestamper.","Start collecting timestamps.","","","","Update the period of the underlying timestamp timer.","The GPIO pins available on the EEM connector, if Pounder …","","The available networking devices on Stabilizer.","The available Pounder-specific hardware interfaces.","The available hardware interfaces on Stabilizer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","","Configure the stabilizer hardware for operation.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A single channel on an ADC peripheral.","","Indicates that the ADC is already in use","An ADC peripheral that can provide ownership of individual …","","","","","","","","Allocate an ADC channel for usage.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new shared ADC driver.","Read the ADC channel and normalize the result.","Read the raw ADC sample for the channel.","","","","","","","","","","Basic configuration for a generated signal.","","","Represents the errors that can occur when attempting to …","The provided amplitude is out-of-range.","The provided frequency is out of range.","The provided symmetry is out of range.","Types of signals that can be generated.","","","","","The amplitude of the output signal in volts.","The full-scale output code of the signal","","","","","","","","","","","Clear the phase accumulator.","","","","","","","","","","","","","","The frequency of the generated signal in Hertz.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","Construct a new signal generator with some specific config.","Get the next value in the generator sequence.","The phase of the output signal in turns.","The frequency tuning word of the signal. Phase is …","The phase offset","","","","","The signal type that should be generated. See Signal …","The type of signal being generated","The normalized symmetry of the signal. At 0% symmetry, the …","","","","","","","","","","","","Convert configuration into signal generator values.","","","","","","Update waveform generation settings.","","","","","","","","","","","","","","Optional input capture preconditioning filter …","The timer used for managing ADC sampling.","Prescalers for externally-supplied reference clocks.","","The timer used for managing ADC sampling.","The timer used for managing ADC sampling.","Optional slave operation modes of a timer.","The timer used for managing ADC sampling.","","","","","","The event that should generate an external trigger from …","Selects the trigger source for the timer peripheral.","","","","","","","","","","","","","","","","","","","","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Get the timer capture/compare channels.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Configure the timer peripheral to generate a trigger based …","Get the period of the timer.","Get the period of the timer.","Get the period of the timer.","Get the period of the timer.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct the sampling timer.","Construct the sampling timer.","Construct the sampling timer.","Construct the sampling timer.","Clock the timer from an external source.","Clock the timer from an external source.","Clock the timer from an external source.","Clock the timer from an external source.","Manually set the period of the timer.","Manually set the period of the timer.","Manually set the period of the timer.","Manually set the period of the timer.","","","","","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Select a trigger source for the timer peripheral.","Start the timer.","Start the timer.","Start the timer.","Start the timer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the timer update event.","Get the timer update event.","Get the timer update event.","Get the timer update event.","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Capture/Compare 1 selection","Capture/Compare 2 selection","Capture/compare 3 selection","Capture/Compare 4 selection","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","A capture/compare channel of the timer.","A capture channel of the timer.","The channels representing the timer.","2: CC2 channel is configured as input, IC2 is mapped on TI1","1: CC1 channel is configured as input, IC1 is mapped on TI1","1: CC2 channel is configured as input, IC2 is mapped on TI2","2: CC1 channel is configured as input, IC1 is mapped on TI2","2: CC4 channel is configured as input, IC4 is mapped on TI3","1: CC3 channel is configured as input, IC3 is mapped on TI3","1: CC4 channel is configured as input, IC4 is mapped on TI4","2: CC3 channel is configured as input, IC3 is mapped on TI4","3: CC2 channel is configured as input, IC2 is mapped on TRC","3: CC1 channel is configured as input, IC1 is mapped on TRC","3: CC4 channel is configured as input, IC4 is mapped on TRC","3: CC3 channel is configured as input, IC3 is mapped on TRC","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","Check if an over-capture event has occurred.","","","","","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture input pre-filter.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Configure the input capture prescaler.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","Enable the input capture to begin capturing timer values.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Operate the channel in input-capture mode.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Get the latest capture from the channel.","Enable DMA requests upon timer updates.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Allow the channel to generate DMA requests.","Create a new update event","Construct a new set of channels.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Operate the channel as an output-compare.","Trigger a DMA request manually","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A structure of Stabilizer’s default network users.","","","","","","","","","","","","","","","Enable live data streaming.","Stabilizer data stream capabilities","","Direct the stream to the provided remote target.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get the MQTT prefix of a device.","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Task to process network hardware.","Construct Stabilizer’s default network users.","","","Stabilizer Telemetry Capabilities","","","","","","","","","","","","","","Update and process all of the network users state.","Streamed data contains ADC0, ADC1, DAC0, and DAC1 …","The “consumer” portion of the data stream.","Streamed data in FLS (fiber length stabilization) format. …","The data generator for a stream.","Specifies the format of streamed data","Represents the destination for the UDP stream to send data …","Reserved, unused format specifier.","Add a batch to the current stream frame.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","Process any data for transmission.","","Configure the remote endpoint of the stream.","Configure streaming on a device.","","","","","","","","","","","","","Processor for managing network hardware.","","","Returns the argument unchanged.","Handle ethernet link connection status.","Calls U::from(self)
.","Construct a new network processor.","","","","","Process and update the state of the network.","The telemetry structure is data that is ultimately …","The telemetry buffer is used for storing sample values …","The telemetry client for reporting telemetry data over …","The latest input sample on ADC0/ADC1.","Most recent input voltage measurement.","","","","","","","","The CPU temperature in degrees Celsius.","The latest output code on DAC0/DAC1.","Most recent output voltage.","","The latest digital input states during processing.","Most recent digital input assertion state.","Convert the telemetry buffer to finalized, SI-unit …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Construct a new telemetry client.","Publish telemetry over MQTT","","","","","","","","","","","Update the telemetry client","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","The interface to read/write data to/from serially (via …","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","Metadata associated with the application","","","","","","","","","","","","","The Settings structure.","","The storage mechanism used to persist settings to between …","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,1,0,0,0,0,1,1,1,0,0,0,8,11,13,8,11,13,8,8,8,8,11,13,8,11,13,11,13,11,13,11,13,8,8,11,13,8,11,13,8,11,13,11,13,33,33,33,33,0,0,33,35,33,35,33,33,33,33,35,33,35,35,33,35,33,35,35,33,33,33,35,33,35,33,0,38,38,38,38,38,38,38,38,38,0,0,0,43,43,43,43,44,45,43,44,45,43,43,43,43,44,45,43,44,45,44,45,44,45,44,45,43,43,44,45,43,44,45,43,44,45,44,45,0,52,52,52,52,52,52,52,52,52,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,55,55,55,55,55,55,55,55,55,55,55,0,58,58,58,58,58,58,58,58,58,58,0,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,0,0,0,66,65,65,65,65,65,66,0,0,0,0,0,65,0,66,67,67,0,66,66,66,65,65,65,65,65,65,65,67,67,0,0,66,0,66,68,69,70,71,0,73,76,65,66,67,68,69,70,71,72,73,76,65,66,67,68,69,70,71,72,71,65,66,67,68,69,70,71,72,73,0,68,69,70,71,72,68,72,65,65,66,67,68,69,70,71,72,68,73,76,65,65,66,66,67,68,69,70,71,72,0,73,76,65,66,67,68,69,70,71,72,65,76,76,70,72,73,76,65,69,68,70,65,73,73,72,76,0,76,76,68,69,70,71,72,76,76,73,0,76,73,76,65,66,67,68,69,70,71,72,73,76,65,66,67,68,69,70,71,72,73,76,65,66,67,68,69,70,71,72,73,0,220,220,220,220,220,0,0,94,95,94,95,94,94,95,94,95,94,94,95,94,95,94,95,95,94,95,0,0,98,98,98,96,98,96,96,98,96,98,96,96,96,98,96,98,96,98,96,0,221,221,0,104,104,104,104,104,104,104,104,104,104,104,0,0,0,0,0,0,0,115,115,115,111,222,223,115,116,109,110,111,222,223,115,116,109,110,109,110,115,116,111,115,111,115,111,222,223,115,116,109,110,111,222,223,115,116,109,110,111,223,223,223,223,222,115,115,222,116,0,111,222,115,111,115,115,115,116,111,222,223,115,116,109,110,111,222,223,115,116,109,110,111,222,223,115,116,109,110,111,115,115,0,0,39,0,42,117,39,42,117,39,39,117,39,42,117,39,42,117,39,117,42,42,42,117,39,42,117,39,42,117,39,0,0,122,0,124,124,124,0,0,122,122,122,123,125,122,123,124,125,121,122,123,124,125,121,121,122,123,124,125,123,125,122,123,122,123,124,125,121,123,122,123,124,125,121,123,123,122,123,124,125,121,121,123,123,121,121,123,125,125,122,123,123,123,123,125,123,123,122,123,124,125,121,122,123,124,125,121,123,122,123,124,125,121,121,139,139,139,139,139,147,145,156,156,145,145,145,139,0,0,0,139,0,0,0,0,147,146,146,146,146,0,0,139,139,146,147,156,145,107,134,136,105,139,146,147,156,145,107,134,136,105,107,134,136,105,139,146,147,156,145,107,134,136,105,107,134,136,105,107,134,136,105,139,146,147,156,145,107,134,136,105,107,134,136,105,107,134,136,105,107,134,136,105,107,134,136,105,107,134,136,105,107,134,136,105,0,0,0,0,139,146,147,156,145,145,107,134,136,105,145,139,146,147,156,145,107,134,136,105,139,146,147,156,145,107,134,136,105,107,134,136,105,0,0,0,0,0,0,0,0,0,0,0,0,0,158,157,158,157,160,159,160,159,158,157,160,159,0,152,153,154,155,148,133,21,152,28,153,48,154,51,155,148,133,21,152,28,153,48,154,51,155,133,133,133,133,152,153,154,155,152,153,154,155,152,153,154,155,152,153,154,155,148,133,21,152,28,153,48,154,51,155,148,133,21,152,28,153,48,154,51,155,21,28,48,51,152,153,154,155,148,21,152,28,153,48,154,51,155,148,133,21,28,48,51,148,148,133,21,152,28,153,48,154,51,155,148,133,21,152,28,153,48,154,51,155,148,133,21,152,28,153,48,154,51,155,0,0,0,0,0,0,0,0,0,0,0,0,0,165,166,165,166,167,168,167,168,165,166,167,168,0,161,162,163,164,149,135,22,161,29,162,170,163,171,164,165,166,167,168,149,135,22,161,29,162,170,163,171,164,165,166,167,168,135,135,135,135,161,162,163,164,165,166,167,168,161,162,163,164,161,162,163,164,161,162,163,164,165,166,167,168,165,166,167,168,149,135,22,161,29,162,170,163,171,164,165,166,167,168,149,135,22,161,29,162,170,163,171,164,165,166,167,168,22,29,170,171,161,162,163,164,149,22,161,29,162,170,163,171,164,149,135,22,29,170,171,149,149,135,22,161,29,162,170,163,171,164,165,166,167,168,149,135,22,161,29,162,170,163,171,164,165,166,167,168,149,135,22,161,29,162,170,163,171,164,165,166,167,168,0,0,0,0,0,0,0,0,0,0,0,0,0,158,157,158,157,160,159,160,159,158,157,160,159,0,172,173,174,175,150,137,176,172,177,173,178,174,62,175,158,157,160,159,150,137,176,172,177,173,178,174,62,175,158,157,160,159,137,137,137,137,172,173,174,175,158,157,160,159,172,173,174,175,172,173,174,175,172,173,174,175,158,157,160,159,158,157,160,159,150,137,176,172,177,173,178,174,62,175,158,157,160,159,150,137,176,172,177,173,178,174,62,175,158,157,160,159,176,177,178,62,172,173,174,175,150,176,172,177,173,178,174,62,175,150,137,176,177,178,62,150,150,137,176,172,177,173,178,174,62,175,158,157,160,159,150,137,176,172,177,173,178,174,62,175,158,157,160,159,150,137,176,172,177,173,178,174,62,175,158,157,160,159,0,0,0,0,0,0,0,0,0,0,0,0,0,183,184,183,184,185,186,185,186,183,184,185,186,0,179,180,181,182,151,138,106,179,187,180,188,181,189,182,183,184,185,186,151,138,106,179,187,180,188,181,189,182,183,184,185,186,138,138,138,138,179,180,181,182,183,184,185,186,179,180,181,182,179,180,181,182,179,180,181,182,183,184,185,186,183,184,185,186,151,138,106,179,187,180,188,181,189,182,183,184,185,186,151,138,106,179,187,180,188,181,189,182,183,184,185,186,106,187,188,189,179,180,181,182,151,106,179,187,180,188,181,189,182,151,138,106,187,188,189,151,151,138,106,179,187,180,188,181,189,182,183,184,185,186,151,138,106,179,187,180,188,181,189,182,183,184,185,186,151,138,106,179,187,180,188,181,189,182,183,184,185,186,0,0,0,0,207,201,201,0,207,201,196,207,201,190,196,207,201,190,190,0,196,190,196,207,201,190,0,0,196,207,201,190,0,190,0,190,190,0,0,190,196,207,201,190,196,207,201,190,196,207,201,190,190,203,0,203,0,0,0,203,191,191,204,202,203,191,204,202,203,202,203,202,202,203,202,203,191,204,202,203,191,204,202,203,202,202,204,202,204,0,191,204,202,203,191,204,202,203,191,204,202,203,0,206,206,206,206,206,206,206,206,206,206,206,0,0,0,208,209,212,208,209,212,208,209,208,209,208,209,208,208,209,208,212,208,209,212,208,209,212,212,209,212,208,209,212,208,209,212,208,209,212,0,216,216,0,0,0,214,213,215,216,214,213,215,216,213,213,214,215,215,215,213,215,216,214,213,215,216,216,213,213,213,214,214,214,213,215,216,215,213,213,214,213,213,215,213,213,214,215,213,215,213,213,214,214,214,214,213,214,213,215,216,214,213,215,216,214,213,215,216],"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,[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,[[1,2],3],[4,1],[-1,-1,[]],0,0,[-1,-2,[],[]],0,0,0,[[1,-1],5,6],0,0,0,0,[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[8,8],[9,8],[10,8],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[11,-1],-2,12,[]],[[13,-1],-2,12,[]],[[[16,[14,15,10]],[18,[17]],[19,[17]],[20,[17]],21,22,23],11],[[[16,[24,15,10]],[25,[17]],[26,[17]],[27,[17]],28,29,23],13],[11,30],[13,30],[-1,[[5,[-2]]],[],[]],[31,[[5,[8,30]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[11,-1],[[5,[-2,32]]],12,[]],[[13,-1],[[5,[-2,32]]],12,[]],0,0,0,0,0,0,[33,31],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[33,33],[-1,[[5,[33]]],34],[[33,2],3],[-1,-1,[]],[-1,-1,[]],[[[35,[-1,-2]]],33,36,36],[-1,-2,[],[]],[-1,-2,[],[]],[[-1,-2],[[35,[-1,-2]]],36,36],[[33,-1],5,6],[[[35,[-1,-2]],33],30,36,36],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[4,[[5,[33,[37,[33]]]]]],[[],[[5,[33,[37,[33]]]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-1,[]],[38,[[5,[31,39]]]],[-1,-2,[],[]],[[[42,[40,41]]],38],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[43,43],[9,43],[10,43],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[44,-1],-2,12,[]],[[45,-1],-2,12,[]],[[[16,[46,15,10]],[47,[17]],48,23],44],[[[16,[49,15,10]],[50,[17]],51,23],45],[44,30],[45,30],[31,[[5,[43,30]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[44,-1],[[5,[-2,32]]],12,[]],[[45,-1],[[5,[-2,32]]],12,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[[52,-1],30,[[54,[53]]]],[[52,-1],30,[[54,[53]]]],[-1,-1,[]],[-1,-2,[],[]],[53,52],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[55,23],[[55,53,53],[[5,[30]]]],[-1,-1,[]],[-1,-2,[],[]],[55,[[56,[53]]]],[[55,53,[57,[4]]],[[5,[30]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[[55,53,[57,[4]]],[[5,[30]]]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-1,[]],[-1,-2,[],[]],[58,[[5,[[59,[53]],[59,[53]]]]]],[[[61,[60]],62],58],[58,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],0,0,[-1,-1,[]],0,0,[-1,-2,[],[]],[1,63],0,0,0,[[63,-1],5,6],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[[],64],[[],30],[[],30],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[65,65],[66,66],[67,67],[68,68],[69,69],[70,70],[71,71],[72,72],[[73,74],[[5,[30,66]]]],0,[-1,[[5,[68]]],34],[-1,[[5,[69]]],34],[-1,[[5,[70]]],34],[-1,[[5,[71]]],34],[-1,[[5,[72]]],34],0,0,[[],[[59,[65]]]],[[65,2],3],[[66,2],3],[[67,2],3],[[68,2],3],[[69,2],3],[[70,2],3],[[71,2],3],[[72,2],3],0,[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[67,65],[-1,-1,[]],[75,66],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[],[[59,[65]]]],[[76,67],[[5,[30,66]]]],0,0,0,[[[78,[77]]],[[5,[73,66]]]],[[[81,[79,80]],[83,[79,82]],[16,[84,15,4]],[42,[85,[87,[86]]]],[42,[88,[89,[86]]]],[42,[40,[90,[86]]]],[42,[40,[91,[86]]]]],[[5,[76,66]]]],[65,[[59,[65]]]],0,0,0,[65,[[59,[65]]]],0,[[73,4,[57,[4]]],[[5,[30,66]]]],0,[76,[[5,[30,66]]]],0,[[76,67],[[5,[31,66]]]],[[76,67],[[5,[31,66]]]],[[68,-1],5,6],[[69,-1],5,6],[[70,-1],5,6],[[71,-1],5,6],[[72,-1],5,6],[[76,64],[[5,[30,66]]]],[[76,65,92],[[5,[30,66]]]],[73,[[5,[30,66]]]],0,[[76,[93,[4]]],[[5,[30,66]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[73,4,[57,[4]]],[[5,[30,66]]]],0,[[-1,67],[[5,[31,66]]],[]],[[-1,67],[[5,[30,66]]],[]],[-1,[[5,[30,66]]],[]],[[-1,67,31],[[5,[31,66]]],[]],[[-1,[93,[4]]],[[5,[30,66]]],[]],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[94,95],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[[73,96,74],94],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[[95,97,[59,[53]],[59,[10]],[59,[53]]],95],[[94,[57,[53]]],30],[95,30],0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[96,98,31,31],30],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[[99,100,101,102,103],96],[96,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],0,[[-1,67],[[5,[31,66]]],[]],[[-1,67],[[5,[31,66]]],[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-1,[]],[-1,-2,[],[]],[104,[[5,[[59,[10]],[59,[10]]]]]],[[105,106,107,[108,[60]],23],104],[104,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[[104,10],30],0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[109,109],[110,110],0,0,[[],111],0,0,0,[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,[[112,113,114,23,53],[[30,[115,[59,[116]]]]]],0,0,0,0,0,0,0,0,[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[39,39],[[[117,[-1]],-2],[[42,[-1,-2]]],[],[[118,[-1]]]],[[39,2],3],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[31,[120,[-1,119]]],[[117,[-1]]],[]],[[[42,[-1,-2]]],[[5,[31,39]]],[],[[118,[-1]]]],[[[42,[-1,-2]]],[[5,[53,39]]],[],[[118,[-1]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[121,30],[122,122],[123,123],[124,124],[125,125],[[],123],[[],125],[-1,[[5,[122]]],34],[[123,-1,-2],[[5,[23,126]]],127,34],[[122,2],3],[[123,2],3],[[124,2],3],[[125,2],3],[[121,2],3],0,[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[-1,128,[57,[4]]],[[5,[23,[126,[129]]]]],[]],[[-1,[57,[23]],[57,[4]]],[[5,[23,[126,[129]]]]],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[],130],[128,[[59,[23]]]],[125,121],[121,[[59,[9]]]],0,0,0,[[122,-1],5,6],[[123,-1,-2],[[5,[23,126]]],127,6],[[-1,128,[57,[4]]],[[5,[23,[126,[131]]]]],[]],[[-1,[57,[23]],[57,[4]]],[[5,[23,[126,[131]]]]],[]],0,0,0,[[-1,-2],[[5,[23,[126,[-3]]]]],127,132,[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[[123,31,31],[[5,[125,124]]]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[121,125],30],0,0,0,0,0,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,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[107,133],[134,135],[136,137],[105,138],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[107,139],30],[[134,139],30],[[136,139],30],[[105,139],30],[107,53],[134,10],[136,53],[105,10],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[141,[140]]],107],[[[141,[142]]],134],[[[141,[143]]],136],[[[141,[144]]],105],[[107,145],30],[[134,145],30],[[136,145],30],[[105,145],30],[[107,53],30],[[134,10],30],[[136,53],30],[[105,10],30],[[107,146,147],30],[[134,146,147],30],[[136,146,147],30],[[105,146,147],30],[[107,146],30],[[134,146],30],[[136,146],30],[[105,146],30],[107,30],[134,30],[136,30],[105,30],0,0,0,0,[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[4,[[5,[145,[37,[145]]]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[[],[[5,[145,[37,[145]]]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[107,148],[134,149],[136,150],[105,151],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[152,23],[153,23],[154,23],[155,23],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,[152,64],[153,64],[154,64],[155,64],[[152,156],30],[[153,156],30],[[154,156],30],[[155,156],30],[[152,145],30],[[153,145],30],[[154,145],30],[[155,145],30],[152,30],[153,30],[154,30],[155,30],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[21,157],152],[[28,158],153],[[48,159],154],[[51,160],155],[152,[[5,[[59,[53]],[59,[53]]]]]],[153,[[5,[[59,[53]],[59,[53]]]]]],[154,[[5,[[59,[53]],[59,[53]]]]]],[155,[[5,[[59,[53]],[59,[53]]]]]],[148,30],[21,30],[152,30],[28,30],[153,30],[48,30],[154,30],[51,30],[155,30],[[],148],[[],133],[[21,53],30],[[28,53],30],[[48,53],30],[[51,53],30],[148,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,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,[161,23],[162,23],[163,23],[164,23],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,[161,64],[162,64],[163,64],[164,64],[165,165],[166,166],[167,167],[168,168],[[161,156],30],[[162,156],30],[[163,156],30],[[164,156],30],[[161,145],30],[[162,145],30],[[163,145],30],[[164,145],30],[161,30],[162,30],[163,30],[164,30],[[165,165],64],[[166,166],64],[[167,167],64],[[168,168],64],[[165,2],[[5,[30,169]]]],[[166,2],[[5,[30,169]]]],[[167,2],[[5,[30,169]]]],[[168,2],[[5,[30,169]]]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[22,166],161],[[29,165],162],[[170,168],163],[[171,167],164],[161,[[5,[[59,[10]],[59,[10]]]]]],[162,[[5,[[59,[10]],[59,[10]]]]]],[163,[[5,[[59,[10]],[59,[10]]]]]],[164,[[5,[[59,[10]],[59,[10]]]]]],[149,30],[22,30],[161,30],[29,30],[162,30],[170,30],[163,30],[171,30],[164,30],[[],149],[[],135],[[22,10],30],[[29,10],30],[[170,10],30],[[171,10],30],[149,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,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,[172,23],[173,23],[174,23],[175,23],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,[172,64],[173,64],[174,64],[175,64],[158,158],[157,157],[160,160],[159,159],[[172,156],30],[[173,156],30],[[174,156],30],[[175,156],30],[[172,145],30],[[173,145],30],[[174,145],30],[[175,145],30],[172,30],[173,30],[174,30],[175,30],[[158,158],64],[[157,157],64],[[160,160],64],[[159,159],64],[[158,2],[[5,[30,169]]]],[[157,2],[[5,[30,169]]]],[[160,2],[[5,[30,169]]]],[[159,2],[[5,[30,169]]]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[176,157],172],[[177,158],173],[[178,159],174],[[62,160],175],[172,[[5,[[59,[53]],[59,[53]]]]]],[173,[[5,[[59,[53]],[59,[53]]]]]],[174,[[5,[[59,[53]],[59,[53]]]]]],[175,[[5,[[59,[53]],[59,[53]]]]]],[150,30],[176,30],[172,30],[177,30],[173,30],[178,30],[174,30],[62,30],[175,30],[[],150],[[],137],[[176,53],30],[[177,53],30],[[178,53],30],[[62,53],30],[150,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,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,[179,23],[180,23],[181,23],[182,23],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,[179,64],[180,64],[181,64],[182,64],[183,183],[184,184],[185,185],[186,186],[[179,156],30],[[180,156],30],[[181,156],30],[[182,156],30],[[179,145],30],[[180,145],30],[[181,145],30],[[182,145],30],[179,30],[180,30],[181,30],[182,30],[[183,183],64],[[184,184],64],[[185,185],64],[[186,186],64],[[183,2],[[5,[30,169]]]],[[184,2],[[5,[30,169]]]],[[185,2],[[5,[30,169]]]],[[186,2],[[5,[30,169]]]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[106,184],179],[[187,183],180],[[188,186],181],[[189,185],182],[179,[[5,[[59,[10]],[59,[10]]]]]],[180,[[5,[[59,[10]],[59,[10]]]]]],[181,[[5,[[59,[10]],[59,[10]]]]]],[182,[[5,[[59,[10]],[59,[10]]]]]],[151,30],[106,30],[179,30],[187,30],[180,30],[188,30],[181,30],[189,30],[182,30],[[],151],[[],138],[[106,10],30],[[187,10],30],[[188,10],30],[[189,10],30],[151,30],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[190,[-1,-2]],-3],191,[192,193,194],195,[[54,[4]]]],0,[[],196],[[[190,[-1,-2]],197],30,[192,193,194],195],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[[128,128],198],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,[[199,200,114,128,128,128,63],[[190,[-1,-2]]],[192,193,194],195],0,0,0,0,[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[[190,[-1,-2]]],201,[192,193,194],195],0,0,0,0,0,0,0,[[191,-1],30,132],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[202,202],[203,203],[[],202],[-1,[[5,[202]]],34],[[203,203],64],[[202,2],3],[[203,2],3],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,[204,30],[[202,-1],5,6],[[204,197],30],[205,[[30,[191,204]]]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-1,[]],[206,30],[-1,-2,[],[]],[[205,200],206],0,[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[206,207],0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[208,208],0,0,0,[[],208],0,0,[[208,33,33,31],209],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[211,[205,114,[210,[205]]]],128,63],[[212,[-1]]],195],[[[212,[-1]],-1],30,195],[[209,-1],5,6],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[[[212,[-1]]],30,195],0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[213,213],[[214,128],30],0,[[],215],[-1,[[5,[215]]],34],[[213,-1,-2],[[5,[23,126]]],127,34],[[[57,[4]]],[[5,[215]]]],[[[216,[-1]],2],3,217],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[218,[[216,[-1]]],[]],[-1,-1,[]],[[-1,128,[57,[4]]],[[5,[23,[126,[129]]]]],[]],[[-1,[57,[23]],[57,[4]]],[[5,[23,[126,[129]]]]],[]],0,0,[214],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[215],0,[[],130],0,[128,[[59,[23]]]],[219,213],0,[[213,55],30],[213,30],[[214,[57,[4]]],[[5,[30]]]],[[215,-1],5,6],[[213,-1,-2],[[5,[23,126]]],127,6],[[215,[57,[4]]],[[5,[23]]]],[[-1,128,[57,[4]]],[[5,[23,[126,[131]]]]],[]],[[-1,[57,[23]],[57,[4]]],[[5,[23,[126,[131]]]]],[]],[214],0,[214],0,[[-1,-2],[[5,[23,[126,[-3]]]]],127,132,[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,7,[]],[-1,7,[]],[-1,7,[]],[-1,7,[]]],"c":[],"p":[[4,"HardwareVersion",3],[3,"Formatter",1732],[6,"Result",1732],[15,"u8"],[4,"Result",1733],[8,"Serializer",1734],[3,"TypeId",1735],[3,"AdcCode",55],[15,"i16"],[15,"u16"],[3,"Adc0Input",55],[8,"FnOnce",1736],[3,"Adc1Input",55],[3,"SPI2",1737],[3,"Enabled",1738],[3,"Spi",1738],[3,"DMA1",1737],[6,"Stream0",1739],[6,"Stream1",1739],[6,"Stream2",1739],[3,"Channel1",815],[3,"Channel1",959],[15,"usize"],[3,"SPI3",1737],[6,"Stream3",1739],[6,"Stream4",1739],[6,"Stream5",1739],[3,"Channel2",815],[3,"Channel2",959],[15,"tuple"],[15,"f32"],[4,"DMAError",1740],[4,"Gain",91],[8,"Deserializer",1741],[3,"ProgrammableGainAmplifier",91],[8,"StatefulOutputPin",1742],[3,"TryFromPrimitiveError",1743],[3,"CpuTempSensor",121],[4,"AdcError",562],[3,"ADC3",1737],[3,"Temperature",1744],[3,"AdcChannel",562],[3,"DacCode",131],[3,"Dac0Output",131],[3,"Dac1Output",131],[3,"SPI4",1737],[6,"Stream6",1739],[3,"Channel3",815],[3,"SPI5",1737],[6,"Stream7",1739],[3,"Channel4",815],[3,"AsmDelay",170],[15,"u32"],[8,"Into",1745],[3,"Flash",195],[3,"Range",1746],[15,"slice"],[3,"InputStamper",208],[4,"Option",1747],[3,"Alternate",1748],[6,"PA3",1749],[3,"Channel4",1143],[3,"ApplicationMetadata",219],[15,"bool"],[4,"GpioPin",239],[4,"Error",239],[4,"Channel",239],[3,"DdsChannelState",239],[3,"ChannelState",239],[3,"InputChannelState",239],[3,"OutputChannelState",239],[3,"DdsClockConfig",239],[3,"QspiInterface",239],[4,"Mode",1750],[4,"XspiError",1751],[3,"PounderDevices",239],[3,"QUADSPI",1737],[3,"Xspi",1751],[6,"I2c1Proxy",3],[3,"Lm75",1752],[3,"Lm75",1753],[4,"Mcp23017",1754],[3,"Mcp230xx",1754],[3,"SPI1",1737],[3,"ADC1",1737],[3,"Analog",1748],[6,"PF11",1755],[3,"ADC2",1737],[6,"PF14",1755],[6,"PF3",1755],[6,"PF4",1755],[4,"Level",1754],[15,"array"],[3,"DdsOutput",416],[3,"ProfileBuilder",416],[3,"HighResTimerE",437],[3,"Channel",1750],[4,"Channel",437],[3,"HRTIM_TIME",1737],[3,"HRTIM_MASTER",1737],[3,"HRTIM_COMMON",1737],[3,"CoreClocks",1756],[3,"Hrtim",1757],[3,"Timestamper",461],[3,"PounderTimestampTimer",677],[3,"Channel1",1327],[3,"SamplingTimer",677],[6,"PA0",1749],[3,"UdpSocketStorage",473],[3,"TcpSocketStorage",473],[3,"NetStorage",473],[3,"Peripherals",1758],[3,"Peripherals",1737],[6,"SystemTimer",3],[3,"StabilizerDevices",473],[3,"PounderDevices",473],[3,"SharedAdc",562],[8,"Channel",1759],[3,"Enabled",1744],[3,"Adc",1744],[3,"SignalGenerator",593],[4,"Signal",593],[3,"BasicConfig",593],[4,"Error",593],[3,"Config",593],[4,"Error",1760],[8,"Iterator",1761],[15,"str"],[4,"Error",1762],[3,"Metadata",1760],[4,"Error",1763],[8,"FnMut",1736],[3,"Channels",815],[3,"ShadowSamplingTimer",677],[3,"Channels",959],[3,"TimestampTimer",677],[3,"Channels",1143],[3,"Channels",1327],[4,"TriggerGenerator",677],[3,"TIM2",1737],[3,"Timer",1764],[3,"TIM3",1737],[3,"TIM5",1737],[3,"TIM8",1737],[4,"Prescaler",677],[4,"TriggerSource",677],[4,"SlaveMode",677],[3,"UpdateEvent",815],[3,"UpdateEvent",959],[3,"UpdateEvent",1143],[3,"UpdateEvent",1327],[3,"Channel1InputCapture",815],[3,"Channel2InputCapture",815],[3,"Channel3InputCapture",815],[3,"Channel4InputCapture",815],[4,"InputFilter",677],[4,"CaptureSource1",1143],[4,"CaptureSource2",1143],[4,"CaptureSource3",1143],[4,"CaptureSource4",1143],[3,"Channel1InputCapture",959],[3,"Channel2InputCapture",959],[3,"Channel3InputCapture",959],[3,"Channel4InputCapture",959],[4,"CaptureSource2",959],[4,"CaptureSource1",959],[4,"CaptureSource4",959],[4,"CaptureSource3",959],[3,"Error",1732],[3,"Channel3",959],[3,"Channel4",959],[3,"Channel1InputCapture",1143],[3,"Channel2InputCapture",1143],[3,"Channel3InputCapture",1143],[3,"Channel4InputCapture",1143],[3,"Channel1",1143],[3,"Channel2",1143],[3,"Channel3",1143],[3,"Channel1InputCapture",1327],[3,"Channel2InputCapture",1327],[3,"Channel3InputCapture",1327],[3,"Channel4InputCapture",1327],[4,"CaptureSource2",1327],[4,"CaptureSource1",1327],[4,"CaptureSource4",1327],[4,"CaptureSource3",1327],[3,"Channel2",1327],[3,"Channel3",1327],[3,"Channel4",1327],[3,"NetworkUsers",1511],[3,"FrameGenerator",1564],[8,"Default",1765],[8,"JsonCoreSlash",1766],[8,"Clone",1767],[8,"Serialize",1734],[3,"MqttStorage",1511],[4,"SocketAddr",1768],[3,"String",1769],[6,"NetworkStack",3],[6,"EthernetPhy",3],[4,"NetworkState",1511],[3,"StreamTarget",1564],[4,"StreamFormat",1564],[3,"DataStream",1564],[6,"NetworkReference",1511],[3,"NetworkProcessor",1613],[4,"UpdateState",1511],[3,"TelemetryBuffer",1625],[3,"Telemetry",1625],[3,"NamedBroker",1770],[3,"Minimq",1771],[3,"TelemetryClient",1625],[3,"Settings",1663],[3,"SerialSettingsPlatform",1663],[3,"SettingsItem",1663],[4,"Error",1663],[8,"Debug",1732],[4,"Error",1772],[3,"Address",1773],[8,"AttenuatorInterface",410],[8,"PowerMeasurementInterface",458],[3,"NetworkDevices",473],[3,"EemGpioDevices",473]],"b":[[65,"impl-From%3Ci16%3E-for-AdcCode"],[66,"impl-From%3Cu16%3E-for-AdcCode"],[144,"impl-From%3Ci16%3E-for-DacCode"],[145,"impl-From%3Cu16%3E-for-DacCode"]]}\
+}');
+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..69480fa305
--- /dev/null
+++ b/firmware/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +
#![no_std]
+
+use bit_field::BitField;
+use bitflags::bitflags;
+use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin};
+
+/// A device driver for the AD9959 direct digital synthesis (DDS) chip.
+///
+/// This chip provides four independently controllable digital-to-analog output sinusoids with
+/// configurable phase, amplitude, and frequency. All channels are inherently synchronized as they
+/// are derived off a common system clock.
+///
+/// The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz.
+///
+/// The chip supports a number of serial interfaces to improve data throughput, including normal,
+/// dual, and quad SPI configurations.
+pub struct Ad9959<INTERFACE> {
+ interface: INTERFACE,
+ reference_clock_frequency: f32,
+ system_clock_multiplier: u8,
+ communication_mode: Mode,
+}
+
+/// A trait that allows a HAL to provide a means of communicating with the AD9959.
+pub trait Interface {
+ type Error;
+
+ fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>;
+
+ fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>;
+
+ fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>;
+}
+
+/// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to
+/// the configuration bits of the DDS CSR register.
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Mode {
+ SingleBitTwoWire = 0b000,
+ SingleBitThreeWire = 0b010,
+ TwoBitSerial = 0b100,
+ FourBitSerial = 0b110,
+}
+
+bitflags! {
+ /// Specifies an output channel of the AD9959 DDS chip.
+ pub struct Channel: u8 {
+ const ONE = 0b00010000;
+ const TWO = 0b00100000;
+ const THREE = 0b01000000;
+ const FOUR = 0b10000000;
+ const ALL = Self::ONE.bits() | Self::TWO.bits() | Self::THREE.bits() | Self::FOUR.bits();
+ }
+}
+
+/// The configuration registers within the AD9959 DDS device. The values of each register are
+/// equivalent to the address.
+#[allow(clippy::upper_case_acronyms)]
+#[repr(u8)]
+pub enum Register {
+ CSR = 0x00,
+ FR1 = 0x01,
+ FR2 = 0x02,
+ CFR = 0x03,
+ CFTW0 = 0x04,
+ CPOW0 = 0x05,
+ ACR = 0x06,
+ LSRR = 0x07,
+ RDW = 0x08,
+ FDW = 0x09,
+ CW1 = 0x0a,
+ CW2 = 0x0b,
+ CW3 = 0x0c,
+ CW4 = 0x0d,
+ CW5 = 0x0e,
+ CW6 = 0x0f,
+ CW7 = 0x10,
+ CW8 = 0x11,
+ CW9 = 0x12,
+ CW10 = 0x13,
+ CW11 = 0x14,
+ CW12 = 0x15,
+ CW13 = 0x16,
+ CW14 = 0x17,
+ CW15 = 0x18,
+}
+
+/// Possible errors generated by the AD9959 driver.
+#[derive(Debug)]
+pub enum Error {
+ Interface,
+ Check,
+ Bounds,
+ Pin,
+ Frequency,
+}
+
+impl<I: Interface> Ad9959<I> {
+ /// Construct and initialize the DDS.
+ ///
+ /// Args:
+ /// * `interface` - An interface to the DDS.
+ /// * `reset_pin` - A pin connected to the DDS reset input.
+ /// * `io_update` - A pin connected to the DDS io_update input.
+ /// * `delay` - A delay implementation for blocking operation for specific amounts of time.
+ /// * `desired_mode` - The desired communication mode of the interface to the DDS.
+ /// * `clock_frequency` - The clock frequency of the reference clock input.
+ /// * `multiplier` - The desired clock multiplier for the system clock. This multiplies
+ /// `clock_frequency` to generate the system clock.
+ pub fn new(
+ interface: I,
+ mut reset_pin: impl OutputPin,
+ io_update: &mut impl OutputPin,
+ delay: &mut impl DelayUs<u8>,
+ desired_mode: Mode,
+ clock_frequency: f32,
+ multiplier: u8,
+ ) -> Result<Self, Error> {
+ let mut ad9959 = Ad9959 {
+ interface,
+ reference_clock_frequency: clock_frequency,
+ system_clock_multiplier: 1,
+ communication_mode: desired_mode,
+ };
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ // Reset the AD9959
+ reset_pin.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ reset_pin.set_low().or(Err(Error::Pin))?;
+
+ ad9959
+ .interface
+ .configure_mode(Mode::SingleBitTwoWire)
+ .or(Err(Error::Interface))?;
+
+ // Program the interface configuration in the AD9959. Default to all channels enabled.
+ let csr = [Channel::ALL.bits() | desired_mode as u8];
+ ad9959.write(Register::CSR, &csr)?;
+
+ // Latch the new interface configuration.
+ io_update.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ ad9959
+ .interface
+ .configure_mode(desired_mode)
+ .or(Err(Error::Interface))?;
+
+ // Empirical evidence indicates a delay is necessary here for the IO update to become
+ // active. This is likely due to needing to wait at least 1 clock cycle of the DDS for the
+ // interface update to occur.
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ // Read back the CSR to ensure it specifies the mode correctly.
+ let mut updated_csr: [u8; 1] = [0];
+ ad9959.read(Register::CSR, &mut updated_csr)?;
+ if updated_csr[0] != csr[0] {
+ return Err(Error::Check);
+ }
+
+ // Set the clock frequency to configure the device as necessary.
+ ad9959.configure_system_clock(clock_frequency, multiplier)?;
+
+ // Latch the new clock configuration.
+ io_update.set_high().or(Err(Error::Pin))?;
+
+ // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed
+ // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to
+ // guarantee conformance with datasheet requirements.
+ delay.delay_us(5);
+
+ io_update.set_low().or(Err(Error::Pin))?;
+
+ Ok(ad9959)
+ }
+
+ fn read(&mut self, reg: Register, data: &mut [u8]) -> Result<(), Error> {
+ self.interface
+ .read(reg as u8, data)
+ .or(Err(Error::Interface))
+ }
+
+ fn write(&mut self, reg: Register, data: &[u8]) -> Result<(), Error> {
+ self.interface
+ .write(reg as u8, data)
+ .or(Err(Error::Interface))
+ }
+
+ /// Configure the internal system clock of the chip.
+ ///
+ /// Arguments:
+ /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core.
+ /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20.
+ ///
+ /// Returns:
+ /// The actual frequency configured for the internal system clock.
+ fn configure_system_clock(
+ &mut self,
+ reference_clock_frequency: f32,
+ multiplier: u8,
+ ) -> Result<f32, Error> {
+ self.reference_clock_frequency = reference_clock_frequency;
+
+ if multiplier != 1 && !(4..=20).contains(&multiplier) {
+ return Err(Error::Bounds);
+ }
+
+ let frequency = multiplier as f32 * self.reference_clock_frequency;
+ if frequency > 500_000_000.0f32 {
+ return Err(Error::Frequency);
+ }
+
+ // TODO: Update / disable any enabled channels?
+ let mut fr1: [u8; 3] = [0, 0, 0];
+ self.read(Register::FR1, &mut fr1)?;
+ fr1[0].set_bits(2..=6, multiplier);
+
+ let vco_range = frequency > 255e6;
+ fr1[0].set_bit(7, vco_range);
+
+ self.write(Register::FR1, &fr1)?;
+ self.system_clock_multiplier = multiplier;
+
+ Ok(self.system_clock_frequency())
+ }
+
+ /// Get the current reference clock frequency in Hz.
+ pub fn get_reference_clock_frequency(&self) -> f32 {
+ self.reference_clock_frequency
+ }
+
+ /// Get the current reference clock multiplier.
+ pub fn get_reference_clock_multiplier(&mut self) -> Result<u8, Error> {
+ let mut fr1: [u8; 3] = [0, 0, 0];
+ self.read(Register::FR1, &mut fr1)?;
+
+ Ok(fr1[0].get_bits(2..=6))
+ }
+
+ /// Perform a self-test of the communication interface.
+ ///
+ /// Note:
+ /// This modifies the existing channel enables. They are restored upon exit.
+ ///
+ /// Returns:
+ /// True if the self test succeeded. False otherwise.
+ pub fn self_test(&mut self) -> Result<bool, Error> {
+ let mut csr: [u8; 1] = [0];
+ self.read(Register::CSR, &mut csr)?;
+ let old_csr = csr[0];
+
+ // Enable all channels.
+ csr[0].set_bits(4..8, 0xF);
+ self.write(Register::CSR, &csr)?;
+
+ // Read back the enable.
+ csr[0] = 0;
+ self.read(Register::CSR, &mut csr)?;
+ if csr[0].get_bits(4..8) != 0xF {
+ return Ok(false);
+ }
+
+ // Clear all channel enables.
+ csr[0].set_bits(4..8, 0x0);
+ self.write(Register::CSR, &csr)?;
+
+ // Read back the enable.
+ csr[0] = 0xFF;
+ self.read(Register::CSR, &mut csr)?;
+ if csr[0].get_bits(4..8) != 0 {
+ return Ok(false);
+ }
+
+ // Restore the CSR.
+ csr[0] = old_csr;
+ self.write(Register::CSR, &csr)?;
+
+ Ok(true)
+ }
+
+ /// Get the current system clock frequency in Hz.
+ fn system_clock_frequency(&self) -> f32 {
+ self.system_clock_multiplier as f32 * self.reference_clock_frequency
+ }
+
+ /// Update an output channel configuration register.
+ ///
+ /// Args:
+ /// * `channel` - The channel to configure.
+ /// * `register` - The register to update.
+ /// * `data` - The contents to write to the provided register.
+ fn modify_channel(
+ &mut self,
+ channel: Channel,
+ register: Register,
+ data: &[u8],
+ ) -> Result<(), Error> {
+ // Disable all other outputs so that we can update the configuration register of only the
+ // specified channel.
+ let csr = [self.communication_mode as u8 | channel.bits()];
+
+ self.write(Register::CSR, &csr)?;
+ self.write(register, data)?;
+
+ Ok(())
+ }
+
+ /// Read a configuration register of a specific channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to read.
+ /// * `register` - The register to read.
+ /// * `data` - A location to store the read register contents.
+ fn read_channel(
+ &mut self,
+ channel: Channel,
+ register: Register,
+ data: &mut [u8],
+ ) -> Result<(), Error> {
+ // Disable all other channels in the CSR so that we can read the configuration register of
+ // only the desired channel.
+ let mut csr = [0];
+ self.read(Register::CSR, &mut csr)?;
+ let new_csr = [self.communication_mode as u8 | channel.bits()];
+
+ self.write(Register::CSR, &new_csr)?;
+ self.read(register, data)?;
+
+ // Restore the previous CSR. Note that the re-enable of the channel happens immediately, so
+ // the CSR update does not need to be latched.
+ self.write(Register::CSR, &csr)?;
+
+ Ok(())
+ }
+
+ /// Configure the phase of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `phase_turns` - The desired phase offset in turns.
+ ///
+ /// Returns:
+ /// The actual programmed phase offset of the channel in turns.
+ pub fn set_phase(
+ &mut self,
+ channel: Channel,
+ phase_turns: f32,
+ ) -> Result<f32, Error> {
+ let phase_offset: u16 =
+ (phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16;
+
+ self.modify_channel(
+ channel,
+ Register::CPOW0,
+ &phase_offset.to_be_bytes(),
+ )?;
+
+ Ok((phase_offset as f32) / ((1 << 14) as f32))
+ }
+
+ /// Get the current phase of a specified channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to get the phase of.
+ ///
+ /// Returns:
+ /// The phase of the channel in turns.
+ pub fn get_phase(&mut self, channel: Channel) -> Result<f32, Error> {
+ let mut phase_offset: [u8; 2] = [0; 2];
+ self.read_channel(channel, Register::CPOW0, &mut phase_offset)?;
+
+ let phase_offset = u16::from_be_bytes(phase_offset) & 0x3FFFu16;
+
+ Ok((phase_offset as f32) / ((1 << 14) as f32))
+ }
+
+ /// Configure the amplitude of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `amplitude` - A normalized amplitude setting [0, 1].
+ ///
+ /// Returns:
+ /// The actual normalized amplitude of the channel relative to full-scale range.
+ pub fn set_amplitude(
+ &mut self,
+ channel: Channel,
+ amplitude: f32,
+ ) -> Result<f32, Error> {
+ if !(0.0..=1.0).contains(&litude) {
+ return Err(Error::Bounds);
+ }
+
+ let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;
+
+ let mut acr: [u8; 3] = [0; 3];
+
+ // Enable the amplitude multiplier for the channel if required. The amplitude control has
+ // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever
+ // full-scale is used.
+ if amplitude_control < (1 << 10) {
+ let masked_control = amplitude_control & 0x3FF;
+ acr[1] = masked_control.to_be_bytes()[0];
+ acr[2] = masked_control.to_be_bytes()[1];
+
+ // Enable the amplitude multiplier
+ acr[1].set_bit(4, true);
+ }
+
+ self.modify_channel(channel, Register::ACR, &acr)?;
+
+ Ok(amplitude_control as f32 / (1 << 10) as f32)
+ }
+
+ /// Get the configured amplitude of a channel.
+ ///
+ /// Args:
+ /// * `channel` - The channel to get the amplitude of.
+ ///
+ /// Returns:
+ /// The normalized amplitude of the channel.
+ pub fn get_amplitude(&mut self, channel: Channel) -> Result<f32, Error> {
+ let mut acr: [u8; 3] = [0; 3];
+ self.read_channel(channel, Register::ACR, &mut acr)?;
+
+ if acr[1].get_bit(4) {
+ let amplitude_control: u16 =
+ (((acr[1] as u16) << 8) | (acr[2] as u16)) & 0x3FF;
+ Ok(amplitude_control as f32 / (1 << 10) as f32)
+ } else {
+ Ok(1.0)
+ }
+ }
+
+ /// Configure the frequency of a specified channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to configure the frequency of.
+ /// * `frequency` - The desired output frequency in Hz.
+ ///
+ /// Returns:
+ /// The actual programmed frequency of the channel.
+ pub fn set_frequency(
+ &mut self,
+ channel: Channel,
+ frequency: f32,
+ ) -> Result<f32, Error> {
+ if frequency < 0.0 || frequency > self.system_clock_frequency() {
+ return Err(Error::Bounds);
+ }
+
+ // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the
+ // frequency tuning word and f_s is the system clock rate.
+ let tuning_word: u32 = ((frequency / self.system_clock_frequency())
+ * 1u64.wrapping_shl(32) as f32)
+ as u32;
+
+ self.modify_channel(
+ channel,
+ Register::CFTW0,
+ &tuning_word.to_be_bytes(),
+ )?;
+ Ok((tuning_word as f32 / 1u64.wrapping_shl(32) as f32)
+ * self.system_clock_frequency())
+ }
+
+ /// Get the frequency of a channel.
+ ///
+ /// Arguments:
+ /// * `channel` - The channel to get the frequency of.
+ ///
+ /// Returns:
+ /// The frequency of the channel in Hz.
+ pub fn get_frequency(&mut self, channel: Channel) -> Result<f32, Error> {
+ // Read the frequency tuning word for the channel.
+ let mut tuning_word: [u8; 4] = [0; 4];
+ self.read_channel(channel, Register::CFTW0, &mut tuning_word)?;
+ let tuning_word = u32::from_be_bytes(tuning_word);
+
+ // Convert the tuning word into a frequency.
+ Ok((tuning_word as f32 * self.system_clock_frequency())
+ / (1u64 << 32) as f32)
+ }
+
+ /// Finalize DDS configuration
+ ///
+ /// # Note
+ /// This is intended for when the DDS profiles will be written as a stream of data to the DDS.
+ ///
+ /// # Returns
+ /// (i, mode) where `i` is the interface to the DDS and `mode` is the frozen `Mode`.
+ pub fn freeze(self) -> (I, Mode) {
+ (self.interface, self.communication_mode)
+ }
+}
+
+/// Represents a means of serializing a DDS profile for writing to a stream.
+pub struct ProfileSerializer {
+ // heapless::Vec<u8, 32>, especially its extend_from_slice() is slow
+ data: [u8; 32],
+ index: usize,
+ // make mode u32 to work around https://github.com/japaric/heapless/issues/305
+ mode: u32,
+}
+
+impl ProfileSerializer {
+ /// Construct a new serializer.
+ ///
+ /// # Args
+ /// * `mode` - The communication mode of the DDS.
+ pub fn new(mode: Mode) -> Self {
+ Self {
+ mode: mode as _,
+ data: [0; 32],
+ index: 0,
+ }
+ }
+
+ /// Update a number of channels with the requested profile.
+ ///
+ /// # Args
+ /// * `channels` - A set of channels to apply the configuration to.
+ /// * `ftw` - If provided, indicates a frequency tuning word for the channels.
+ /// * `pow` - If provided, indicates a phase offset word for the channels.
+ /// * `acr` - If provided, indicates the amplitude control register for the channels. The ACR
+ /// should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used,
+ /// the "Amplitude multiplier enable" bit must be set.
+ #[inline]
+ pub fn update_channels(
+ &mut self,
+ channels: Channel,
+ ftw: Option<u32>,
+ pow: Option<u16>,
+ acr: Option<u32>,
+ ) {
+ let csr = [self.mode as u8 | channels.bits()];
+ self.add_write(Register::CSR, &csr);
+
+ if let Some(ftw) = ftw {
+ self.add_write(Register::CFTW0, &ftw.to_be_bytes());
+ }
+
+ if let Some(pow) = pow {
+ self.add_write(Register::CPOW0, &pow.to_be_bytes());
+ }
+
+ if let Some(acr) = acr {
+ self.add_write(Register::ACR, &acr.to_be_bytes()[1..]);
+ }
+ }
+
+ /// Add a register write to the serialization data.
+ fn add_write(&mut self, register: Register, value: &[u8]) {
+ let data = &mut self.data[self.index..];
+ data[0] = register as u8;
+ data[1..][..value.len()].copy_from_slice(value);
+ self.index += value.len() + 1;
+ }
+
+ #[inline]
+ fn pad(&mut self) {
+ // Pad the buffer to 32-bit (4 byte) alignment by adding dummy writes to CSR and LSRR.
+ // In the case of 1 byte padding, this instead pads with 5 bytes as there is no
+ // valid single-byte write that could be used.
+ if self.index & 1 != 0 {
+ // Pad with 3 bytes
+ self.add_write(Register::LSRR, &[0, 0]);
+ }
+ if self.index & 2 != 0 {
+ // Pad with 2 bytes
+ self.add_write(Register::CSR, &[self.mode as _]);
+ }
+ debug_assert_eq!(self.index & 3, 0);
+ }
+
+ /// Get the serialized profile as a slice of 32-bit words.
+ ///
+ /// # Note
+ /// The serialized profile will be padded to the next 32-bit word boundary by adding dummy
+ /// writes to the CSR or LSRR registers.
+ ///
+ /// # Returns
+ /// A slice of `u32` words representing the serialized profile.
+ #[inline]
+ pub fn finalize(&mut self) -> &[u32] {
+ self.pad();
+ bytemuck::cast_slice(&self.data[..self.index])
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +
//! # 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, SerialTerminal, SystemTimer, Systick,
+ UsbDevice, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Tree,
+ telemetry::{Telemetry, TelemetryBuffer},
+ NetworkState, NetworkUsers,
+ },
+};
+
+const SCALE: f32 = i16::MAX as _;
+
+// The number of cascaded IIR biquads per channel. Select 1 or 2!
+const IIR_CASCADE_LENGTH: usize = 1;
+
+// The number of samples in each batch process
+const BATCH_SIZE: usize = 8;
+
+// The logarithm of the number of 100MHz timer ticks between each sample. With a value of 2^7 =
+// 128, there is 1.28uS per sample, corresponding to a sampling frequency of 781.25 KHz.
+const SAMPLE_TICKS_LOG2: u8 = 7;
+const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2;
+const SAMPLE_PERIOD: f32 =
+ SAMPLE_TICKS as f32 * hardware::design_parameters::TIMER_PERIOD;
+
+#[derive(Clone, Copy, Debug, Tree)]
+pub struct Settings {
+ /// Configure the Analog Front End (AFE) gain.
+ ///
+ /// # Path
+ /// `afe/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// Any of the variants of [Gain] enclosed in double quotes.
+ #[tree]
+ afe: [Gain; 2],
+
+ /// Configure the IIR filter parameters.
+ ///
+ /// # Path
+ /// `iir_ch/<n>/<m>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ /// * `<m>` specifies which cascade to configure. `<m>` := [0, 1], depending on [IIR_CASCADE_LENGTH]
+ ///
+ /// See [iir::Biquad]
+ #[tree(depth(2))]
+ iir_ch: [[iir::Biquad<f32>; IIR_CASCADE_LENGTH]; 2],
+
+ /// Specified true if DI1 should be used as a "hold" input.
+ ///
+ /// # Path
+ /// `allow_hold`
+ ///
+ /// # Value
+ /// "true" or "false"
+ allow_hold: bool,
+
+ /// Specified true if "hold" should be forced regardless of DI1 state and hold allowance.
+ ///
+ /// # Path
+ /// `force_hold`
+ ///
+ /// # Value
+ /// "true" or "false"
+ force_hold: bool,
+
+ /// Specifies the telemetry output period in seconds.
+ ///
+ /// # Path
+ /// `telemetry_period`
+ ///
+ /// # Value
+ /// Any non-zero value less than 65536.
+ telemetry_period: u16,
+
+ /// Specifies the target for data livestreaming.
+ ///
+ /// # Path
+ /// `stream_target`
+ ///
+ /// # Value
+ /// See [StreamTarget#miniconf]
+ stream_target: StreamTarget,
+
+ /// Specifies the config for signal generators to add on to DAC0/DAC1 outputs.
+ ///
+ /// # Path
+ /// `signal_generator/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// See [signal_generator::BasicConfig#miniconf]
+ #[tree(depth(2))]
+ signal_generator: [signal_generator::BasicConfig; 2],
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ let mut i = iir::Biquad::IDENTITY;
+ i.set_min(-SCALE);
+ i.set_max(SCALE);
+ 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: [[i; IIR_CASCADE_LENGTH]; 2],
+
+ // Permit the DI1 digital input to suppress filter output updates.
+ allow_hold: false,
+ // Force suppress filter output updates.
+ force_hold: false,
+ // The default telemetry period in seconds.
+ telemetry_period: 10,
+
+ signal_generator: [signal_generator::BasicConfig::default(); 2],
+
+ stream_target: StreamTarget::default(),
+ }
+ }
+}
+
+#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, LTDC, SDMMC])]
+mod app {
+ use super::*;
+
+ #[monotonic(binds = SysTick, default = true, priority = 2)]
+ type Monotonic = Systick;
+
+ #[shared]
+ struct Shared {
+ usb: UsbDevice,
+ network: NetworkUsers<Settings, Telemetry, 3>,
+
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ signal_generator: [SignalGenerator; 2],
+ }
+
+ #[local]
+ struct Local {
+ usb_terminal: SerialTerminal,
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ iir_state: [[[f32; 4]; 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 settings = stabilizer.usb_serial.settings();
+ let mut network = NetworkUsers::new(
+ stabilizer.net.stack,
+ stabilizer.net.phy,
+ clock,
+ env!("CARGO_BIN_NAME"),
+ &settings.broker,
+ &settings.id,
+ stabilizer.metadata,
+ );
+
+ let generator = network.configure_streaming(StreamFormat::AdcDacData);
+
+ let settings = Settings::default();
+
+ let shared = Shared {
+ usb: stabilizer.usb,
+ 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 {
+ usb_terminal: stabilizer.usb_serial,
+ sampling_timer: stabilizer.adc_dac_timer,
+ digital_inputs: stabilizer.digital_inputs,
+ afes: stabilizer.afes,
+ adcs: stabilizer.adcs,
+ dacs: stabilizer.dacs,
+ iir_state: [[[0.; 4]; IIR_CASCADE_LENGTH]; 2],
+ generator,
+ cpu_temp_sensor: stabilizer.temperature_sensor,
+ };
+
+ // Enable ADC/DAC events
+ local.adcs.0.start();
+ local.adcs.1.start();
+ local.dacs.0.start();
+ local.dacs.1.start();
+
+ // Spawn a settings update for default settings.
+ settings_update::spawn().unwrap();
+ telemetry::spawn().unwrap();
+ ethernet_link::spawn().unwrap();
+ usb::spawn().unwrap();
+ start::spawn_after(100.millis()).unwrap();
+
+ (shared, local, init::Monotonics(stabilizer.systick))
+ }
+
+ #[task(priority = 1, local=[sampling_timer])]
+ fn start(c: start::Context) {
+ // Start sampling ADCs and DACs.
+ c.local.sampling_timer.start();
+ }
+
+ /// Main DSP processing routine.
+ ///
+ /// # Note
+ /// Processing time for the DSP application code is bounded by the following constraints:
+ ///
+ /// DSP application code starts after the ADC has generated a batch of samples and must be
+ /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer
+ /// time). If this constraint is not met, firmware will panic due to an ADC input overrun.
+ ///
+ /// The DSP application code must also fill out the next DAC output buffer in time such that the
+ /// DAC can switch to it when it has completed the current buffer. If this constraint is not met
+ /// it's possible that old DAC codes will be generated on the output and the output samples will
+ /// be delayed by 1 batch.
+ ///
+ /// Because the ADC and DAC operate at the same rate, these two constraints actually implement
+ /// the same time bounds, meeting one also means the other is also met.
+ #[task(binds=DMA1_STR4, local=[digital_inputs, adcs, dacs, iir_state, generator], shared=[settings, signal_generator, telemetry], priority=3)]
+ #[link_section = ".itcm.process"]
+ fn process(c: process::Context) {
+ let process::SharedResources {
+ settings,
+ telemetry,
+ signal_generator,
+ } = c.shared;
+
+ let process::LocalResources {
+ digital_inputs,
+ adcs: (adc0, adc1),
+ dacs: (dac0, dac1),
+ iir_state,
+ generator,
+ } = c.local;
+
+ (settings, telemetry, signal_generator).lock(
+ |settings, telemetry, signal_generator| {
+ let digital_inputs =
+ [digital_inputs.0.is_high(), digital_inputs.1.is_high()];
+ telemetry.digital_inputs = digital_inputs;
+
+ let hold = settings.force_hold
+ || (digital_inputs[1] && settings.allow_hold);
+
+ (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
+ let adc_samples = [adc0, adc1];
+ let dac_samples = [dac0, dac1];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+
+ for channel in 0..adc_samples.len() {
+ adc_samples[channel]
+ .iter()
+ .zip(dac_samples[channel].iter_mut())
+ .zip(&mut signal_generator[channel])
+ .map(|((ai, di), signal)| {
+ let x = f32::from(*ai as i16);
+ let y = settings.iir_ch[channel]
+ .iter()
+ .zip(iir_state[channel].iter_mut())
+ .fold(x, |yi, (ch, state)| {
+ let filter = if hold {
+ &iir::Biquad::HOLD
+ } else {
+ ch
+ };
+
+ filter.update(state, yi)
+ });
+
+ // Note(unsafe): The filter limits must ensure that the value is in range.
+ // The truncation introduces 1/2 LSB distortion.
+ let y: i16 = unsafe { y.to_int_unchecked() };
+
+ let y = y.saturating_add(signal);
+
+ // Convert to DAC code
+ *di = DacCode::from(y).0;
+ })
+ .last();
+ }
+
+ // Stream the data.
+ const N: usize = BATCH_SIZE * core::mem::size_of::<i16>();
+ generator.add(|buf| {
+ for (data, buf) in adc_samples
+ .iter()
+ .chain(dac_samples.iter())
+ .zip(buf.chunks_exact_mut(N))
+ {
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ data.as_ptr() as *const MaybeUninit<u8>,
+ N,
+ )
+ };
+ buf.copy_from_slice(data)
+ }
+ N * 4
+ });
+ // Update telemetry measurements.
+ telemetry.adcs = [
+ AdcCode(adc_samples[0][0]),
+ AdcCode(adc_samples[1][0]),
+ ];
+
+ telemetry.dacs = [
+ DacCode(dac_samples[0][0]),
+ DacCode(dac_samples[1][0]),
+ ];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+ });
+ },
+ );
+ }
+
+ #[idle(shared=[network, usb])]
+ fn idle(mut c: idle::Context) -> ! {
+ loop {
+ match c.shared.network.lock(|net| net.update()) {
+ NetworkState::SettingsChanged(_path) => {
+ settings_update::spawn().unwrap()
+ }
+ NetworkState::Updated => {}
+ NetworkState::NoChange => {
+ // We can't sleep if USB is not in suspend.
+ if c.shared.usb.lock(|usb| {
+ usb.state()
+ == usb_device::device::UsbDeviceState::Suspend
+ }) {
+ cortex_m::asm::wfi();
+ }
+ }
+ }
+ }
+ }
+
+ #[task(priority = 1, local=[afes], shared=[network, settings, signal_generator])]
+ fn settings_update(mut c: settings_update::Context) {
+ let settings = c.shared.network.lock(|net| *net.miniconf.settings());
+ c.shared.settings.lock(|current| *current = settings);
+
+ c.local.afes.0.set_gain(settings.afe[0]);
+ c.local.afes.1.set_gain(settings.afe[1]);
+
+ // Update the signal generators
+ for (i, &config) in settings.signal_generator.iter().enumerate() {
+ match config.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) {
+ Ok(config) => {
+ c.shared
+ .signal_generator
+ .lock(|generator| generator[i].update_waveform(config));
+ }
+ Err(err) => log::error!(
+ "Failed to update signal generation on DAC{}: {:?}",
+ i,
+ err
+ ),
+ }
+ }
+
+ let target = settings.stream_target.into();
+ c.shared.network.lock(|net| net.direct_stream(target));
+ }
+
+ #[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])]
+ fn telemetry(mut c: telemetry::Context) {
+ let telemetry: TelemetryBuffer =
+ c.shared.telemetry.lock(|telemetry| *telemetry);
+
+ let (gains, telemetry_period) = c
+ .shared
+ .settings
+ .lock(|settings| (settings.afe, settings.telemetry_period));
+
+ c.shared.network.lock(|net| {
+ net.telemetry.publish(&telemetry.finalize(
+ gains[0],
+ gains[1],
+ c.local.cpu_temp_sensor.get_temperature().unwrap(),
+ ))
+ });
+
+ // Schedule the telemetry task in the future.
+ telemetry::Monotonic::spawn_after((telemetry_period as u64).secs())
+ .unwrap();
+ }
+
+ #[task(priority = 1, shared=[usb], local=[usb_terminal])]
+ fn usb(mut c: usb::Context) {
+ // Handle the USB serial terminal.
+ c.shared.usb.lock(|usb| {
+ usb.poll(&mut [c.local.usb_terminal.interface_mut().inner_mut()]);
+ });
+
+ c.local.usb_terminal.process().unwrap();
+
+ // Schedule to run this task every 10 milliseconds.
+ usb::spawn_after(10u64.millis()).unwrap();
+ }
+
+ #[task(priority = 1, shared=[network])]
+ fn ethernet_link(mut c: ethernet_link::Context) {
+ c.shared.network.lock(|net| net.processor.handle_link());
+ ethernet_link::Monotonic::spawn_after(1.secs()).unwrap();
+ }
+
+ #[task(binds = ETH, priority = 1)]
+ fn eth(_: eth::Context) {
+ unsafe { hal::ethernet::interrupt_handler() }
+ }
+
+ #[task(binds = SPI2, priority = 4)]
+ fn spi2(_: spi2::Context) {
+ panic!("ADC0 SPI error");
+ }
+
+ #[task(binds = SPI3, priority = 4)]
+ fn spi3(_: spi3::Context) {
+ panic!("ADC1 SPI error");
+ }
+
+ #[task(binds = SPI4, priority = 4)]
+ fn spi4(_: spi4::Context) {
+ panic!("DAC0 SPI error");
+ }
+
+ #[task(binds = SPI5, priority = 4)]
+ fn spi5(_: spi5::Context) {
+ panic!("DAC1 SPI error");
+ }
+}
+
use num_traits::ops::wrapping::WrappingAdd;
+
+/// Wrapping Accumulator
+#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
+pub struct Accu<T> {
+ state: T,
+ step: T,
+}
+
+impl<T> Accu<T> {
+ /// Create a new accumulator with given initial state and step.
+ pub fn new(state: T, step: T) -> Self {
+ Self { state, step }
+ }
+}
+
+impl<T> Iterator for Accu<T>
+where
+ T: WrappingAdd + Copy,
+{
+ type Item = T;
+ fn next(&mut self) -> Option<T> {
+ let s = self.state;
+ self.state = s.wrapping_add(&self.step);
+ Some(s)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +
fn divi(mut y: u32, mut x: u32) -> u32 {
+ debug_assert!(y <= x);
+ let z = y.leading_zeros().min(15);
+ y <<= z;
+ x += (1 << (15 - z)) - 1;
+ x >>= 16 - z;
+ if x == 0 {
+ 0 // x == y == 0
+ } else {
+ ((y / x) << 15) + (1 << 14)
+ }
+}
+
+fn atani(x: u32) -> u32 {
+ const A: [i32; 6] = [
+ 0x0517c2cd,
+ -0x06c6496b,
+ 0x0fbdb021,
+ -0x25b32e0a,
+ 0x43b34c81,
+ -0x3bc823dd,
+ ];
+ let x = x as i64;
+ let x2 = ((x * x) >> 32) as i32 as i64;
+ let r = A
+ .iter()
+ .rev()
+ .fold(0, |r, a| ((r as i64 * x2) >> 32) as i32 + a);
+ ((r as i64 * x) >> 28) as _
+}
+
+/// 2-argument arctangent function.
+///
+/// This implementation uses all integer arithmetic for fast
+/// computation.
+///
+/// # Arguments
+///
+/// * `y` - Y-axis component.
+/// * `x` - X-axis component.
+///
+/// # Returns
+///
+/// The angle between the x-axis and the ray to the point (x,y). The
+/// result range is from i32::MIN to i32::MAX, where i32::MIN
+/// represents -pi and, equivalently, +pi. i32::MAX represents one
+/// count less than +pi.
+pub fn atan2(mut y: i32, mut x: i32) -> i32 {
+ let mut k = 0u32;
+ if y < 0 {
+ y = y.saturating_neg();
+ k ^= u32::MAX;
+ }
+ if x < 0 {
+ x = x.saturating_neg();
+ k ^= u32::MAX >> 1;
+ }
+ if y > x {
+ (y, x) = (x, y);
+ k ^= u32::MAX >> 2;
+ }
+ let r = atani(divi(y as _, x as _));
+ (r ^ k) as _
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use core::f64::consts::PI;
+
+ #[test]
+ fn atan2_error() {
+ const N: isize = 201;
+ for i in 0..N {
+ let p = ((1. - 2. * i as f64 / N as f64) * i32::MIN as f64) as i32;
+ let pf = p as f64 / i32::MIN as f64 * -PI;
+ let y = (pf.sin() * i32::MAX as f64) as i32;
+ let x = (pf.cos() * i32::MAX as f64) as i32;
+ let _p0 = (y as f64).atan2(x as f64);
+ let pp = atan2(y, x);
+ let pe = -(pp as f64 / i32::MIN as f64);
+ println!(
+ "y:{:.5e}, x:{:.5e}, p/PI:{:.5e}: pe:{:.5e}, pe*PI-p0:{:.5e}",
+ y as f64 / i32::MAX as f64,
+ x as f64 / i32::MAX as f64,
+ pf / PI,
+ pe,
+ pe * PI - pf
+ );
+ }
+ }
+
+ fn angle_to_axis(angle: f64) -> f64 {
+ let angle = angle % (PI / 2.);
+ (PI / 2. - angle).min(angle)
+ }
+
+ #[test]
+ fn atan2_absolute_error() {
+ const N: usize = 321;
+ let mut test_vals = [0i32; N + 2];
+ let scale = (1i64 << 31) as f64;
+ for i in 0..N {
+ test_vals[i] = (scale * (-1. + 2. * i as f64 / N as f64)) as i32;
+ }
+
+ assert!(test_vals.contains(&i32::MIN));
+ test_vals[N] = i32::MAX;
+ test_vals[N + 1] = 0;
+
+ let mut rms_err = 0f64;
+ let mut abs_err = 0f64;
+ let mut rel_err = 0f64;
+
+ for &x in test_vals.iter() {
+ for &y in test_vals.iter() {
+ let want = (y as f64).atan2(x as f64);
+ let have = atan2(y, x) as f64 * (PI / scale);
+ let err = (have - want).abs();
+ abs_err = abs_err.max(err);
+ rms_err += err * err;
+ if err > 3e-5 {
+ println!("{:.5e}/{:.5e}: {:.5e} vs {:.5e}", y, x, have, want);
+ println!("y/x {} {}", y, x);
+ rel_err = rel_err.max(err / angle_to_axis(want));
+ }
+ }
+ }
+ rms_err = rms_err.sqrt() / test_vals.len() as f64;
+ println!("max abs err: {:.2e}", abs_err);
+ println!("rms abs err: {:.2e}", rms_err);
+ println!("max rel err: {:.2e}", rel_err);
+ assert!(abs_err < 1.2e-5);
+ assert!(rms_err < 4.2e-6);
+ assert!(rel_err < 1e-12);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +
pub use num_complex::Complex;
+
+use super::{atan2, cossin};
+
+/// Complex extension trait offering DSP (fast, good accuracy) functionality.
+pub trait ComplexExt<T, U> {
+ /// Unit magnitude from angle
+ fn from_angle(angle: T) -> Self;
+ /// Square of magnitude
+ fn abs_sqr(&self) -> U;
+ /// Log2 approximation
+ fn log2(&self) -> T;
+ /// Angle
+ fn arg(&self) -> T;
+ /// Staturating addition
+ fn saturating_add(&self, other: Self) -> Self;
+ /// Saturating subtraction
+ 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> {
+ /// Scaled multiplication for fixed point
+ fn mul_scaled(self, other: T) -> Self;
+}
+
+impl MulScaled<Complex<i32>> for Complex<i32> {
+ fn mul_scaled(self, other: Self) -> Self {
+ let a = self.re as i64;
+ let b = self.im as i64;
+ let c = other.re as i64;
+ let d = other.im as i64;
+ Complex {
+ re: ((a * c - b * d) >> 31) as i32,
+ im: ((b * c + a * d) >> 31) as i32,
+ }
+ }
+}
+
+impl MulScaled<i32> for Complex<i32> {
+ fn mul_scaled(self, other: i32) -> Self {
+ Complex {
+ re: ((other as i64 * self.re as i64) >> 31) as i32,
+ im: ((other as i64 * self.im as i64) >> 31) as i32,
+ }
+ }
+}
+
+impl MulScaled<i16> for Complex<i32> {
+ fn mul_scaled(self, other: i16) -> Self {
+ Complex {
+ re: (other as i32 * (self.re >> 16) + (1 << 14)) >> 15,
+ im: (other as i32 * (self.im >> 16) + (1 << 14)) >> 15,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +
include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
+
+/// Compute the cosine and sine of an angle.
+/// This is ported from the MiSoC cossin core.
+/// <https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py>
+///
+/// # Arguments
+/// * `phase` - 32-bit phase where `i32::MIN` is -π and `i32::MAX` is π
+///
+/// # 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 +
/// Single inpout single output i32 filter
+pub trait Filter {
+ /// Filter configuration type.
+ ///
+ /// While the filter struct owns the state,
+ /// the configuration is decoupled to allow sharing.
+ type Config;
+ /// Update the filter with a new sample.
+ ///
+ /// # Args
+ /// * `x`: Input data.
+ /// * `k`: Filter configuration.
+ ///
+ /// # Return
+ /// Filtered output y.
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32;
+ /// Return the current filter output
+ fn get(&self) -> i32;
+ /// Update the filter so that it outputs the provided value.
+ /// This does not completely define the state of the filter.
+ fn set(&mut self, x: i32);
+}
+
+/// Nyquist zero
+///
+/// Filter with a flat transfer function and a transfer function zero at Nyquist.
+#[derive(Copy, Clone, Default)]
+pub struct Nyquist(i32);
+impl Filter for Nyquist {
+ type Config = ();
+ fn update(&mut self, x: i32, _k: &Self::Config) -> i32 {
+ let x = x >> 1; // x/2 for less bias but more distortion
+ let y = x.wrapping_add(self.0);
+ self.0 = x;
+ y
+ }
+ fn get(&self) -> i32 {
+ self.0
+ }
+ fn set(&mut self, x: i32) {
+ self.0 = x;
+ }
+}
+
+/// Repeat another filter
+#[derive(Copy, Clone)]
+pub struct Repeat<const N: usize, T>([T; N]);
+impl<const N: usize, T: Filter> Filter for Repeat<N, T> {
+ type Config = T::Config;
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ self.0.iter_mut().fold(x, |x, stage| stage.update(x, k))
+ }
+ fn get(&self) -> i32 {
+ self.0[N - 1].get()
+ }
+ fn set(&mut self, x: i32) {
+ self.0.iter_mut().for_each(|stage| stage.set(x));
+ }
+}
+impl<const N: usize, T: Default + Copy> Default for Repeat<N, T> {
+ fn default() -> Self {
+ Self([T::default(); N])
+ }
+}
+
+/// Combine two different filters in cascade
+#[derive(Copy, Clone, Default)]
+pub struct Cascade<T, U>(T, U);
+impl<T: Filter, U: Filter> Filter for Cascade<T, U> {
+ type Config = (T::Config, U::Config);
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ self.1.update(self.0.update(x, &k.0), &k.1)
+ }
+ fn get(&self) -> i32 {
+ self.1.get()
+ }
+ fn set(&mut self, x: i32) {
+ self.1.set(x)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +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 +
//! Half-band filters and cascades
+//!
+//! Used to perform very efficient high-dynamic range rate changes by powers of two.
+
+use core::{
+ iter::Sum,
+ ops::{Add, Mul},
+};
+
+use num_traits::Zero;
+
+/// Filter input items into output items.
+pub trait Filter {
+ /// Input/output item type.
+ // TODO: impl with generic item type
+ type Item;
+
+ /// Process a block of items.
+ ///
+ /// Input items can be either in `x` or in `y`.
+ /// In the latter case the filtering operation is done in-place.
+ /// Output is always written into `y`.
+ /// The slice of items written into `y` is returned.
+ /// Input and output size relations must match the filter requirements
+ /// (decimation/interpolation and maximum block size).
+ /// When using in-place operation, `y` needs to contain the input items
+ /// (fewer than `y.len()` in the case of interpolation) and must be able to
+ /// contain the output items.
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item];
+
+ /// Return the block size granularity and the maximum block size.
+ ///
+ /// For in-place processing, this refers to constraints on `y`.
+ /// Otherwise this refers to the larger of `x` and `y` (`x` for decimation and `y` for interpolation).
+ /// The granularity is also the rate change in the case of interpolation/decimation filters.
+ fn block_size(&self) -> (usize, usize);
+
+ /// Finite impulse response length in numer of output items minus one
+ /// Get this many to drain all previous memory
+ fn response_length(&self) -> usize;
+
+ // TODO: process items with automatic blocks
+ // fn process(&mut self, x: Option<&[Self::Item]>, y: &mut [Self::Item]) -> usize {}
+}
+
+/// Symmetric FIR filter prototype.
+///
+/// # Generics
+/// * `M`: number of taps, one-sided. The filter has effectively 2*M DSP taps
+/// * `N`: state size: N = 2*M - 1 + {input/output}.len()
+///
+/// # Half band decimation/interpolation filters
+///
+/// Half-band filters (rate change of 2) and cascades of HBFs are implemented in
+/// [`HbfDec`] and [`HbfInt`] etc.
+/// The half-band filter has unique properties that make it preferrable in many cases:
+///
+/// * only needs M multiplications (fused multiply accumulate) for 4*M taps
+/// * HBF decimator stores less state than a generic FIR filter
+/// * as a FIR filter has linear phase/flat group delay
+/// * very small passband ripple and excellent stopband attenuation
+/// * as a cascade of decimation/interpolation filters, the higher-rate filters
+/// need successively fewer taps, allowing the filtering to be dominated by
+/// only the highest rate filter with the fewest taps
+/// * In a cascade of HBF the overall latency, group delay, and impulse response
+/// length are dominated by the lowest-rate filter which, due to its manageable transition
+/// band width (compared to single-stage filters) can be smaller, shorter, and faster.
+/// * high dynamic range and inherent stability compared with an IIR filter
+/// * can be combined with a CIC filter for non-power-of-two or even higher rate changes
+///
+/// The implementations here are all `no_std` and `no-alloc`.
+/// They support (but don't require) in-place filtering to reduce memory usage.
+/// They unroll and optimmize extremely well targetting current architectures,
+/// e.g. requiring less than 4 instructions per input item for the full `HbfDecCascade` on Skylake.
+/// The filters are optimized for decent block sizes and perform best (i.e. with negligible
+/// overhead) for blocks of 32 high-rate items or more, depending very much on architecture.
+
+#[derive(Clone, Debug, Copy)]
+pub struct SymFir<'a, T, const M: usize, const N: usize> {
+ x: [T; N],
+ taps: &'a [T; M],
+}
+
+impl<'a, T: Copy + Zero + Add + Mul<Output = T> + Sum, const M: usize, const N: usize>
+ SymFir<'a, T, M, N>
+{
+ /// Create a new `SymFir`.
+ ///
+ /// # Args
+ /// * `taps`: one-sided FIR coefficients, expluding center tap, oldest to one-before-center
+ pub fn new(taps: &'a [T; M]) -> Self {
+ debug_assert!(N >= M * 2);
+ Self {
+ x: [T::zero(); N],
+ taps,
+ }
+ }
+
+ /// Obtain a mutable reference to the input items buffer space.
+ #[inline]
+ pub fn buf_mut(&mut self) -> &mut [T] {
+ &mut self.x[2 * M - 1..]
+ }
+
+ /// Perform the FIR convolution and yield results iteratively.
+ #[inline]
+ pub fn get(&self) -> impl Iterator<Item = T> + '_ {
+ self.x.windows(2 * M).map(|x| {
+ let (old, new) = x.split_at(M);
+ old.iter()
+ .zip(new.iter().rev())
+ .zip(self.taps.iter())
+ .map(|((xo, xn), tap)| (*xo + *xn) * *tap)
+ .sum()
+ })
+ }
+
+ /// Move items as new filter state.
+ ///
+ /// # Args
+ /// * `offset`: Keep the `2*M-1` items at `offset` as the new filter state.
+ #[inline]
+ pub fn keep_state(&mut self, offset: usize) {
+ self.x.copy_within(offset..offset + 2 * M - 1, 0);
+ }
+}
+
+// TODO: pub struct SymFirInt<R>, SymFirDec<R>
+
+/// Half band decimator (decimate by two)
+///
+/// The effective number of DSP taps is 4*M - 1.
+///
+/// M: number of taps
+/// N: state size: N = 2*M - 1 + output.len()
+#[derive(Clone, Debug, Copy)]
+pub struct HbfDec<'a, T, const M: usize, const N: usize> {
+ even: [T; N], // This is an upper bound to N - M (unstable const expr)
+ odd: SymFir<'a, T, M, N>,
+}
+
+impl<'a, T: Zero + Copy + Add + Mul<Output = T> + Sum, const M: usize, const N: usize>
+ HbfDec<'a, T, M, N>
+{
+ /// Create a new `HbfDec`.
+ ///
+ /// # Args
+ /// * `taps`: The FIR filter coefficients. Only the non-zero (odd) taps
+ /// from oldest to one-before-center. Normalized such that center tap is 1.
+ pub fn new(taps: &'a [T; M]) -> Self {
+ Self {
+ even: [T::zero(); N],
+ odd: SymFir::new(taps),
+ }
+ }
+}
+
+trait Half {
+ fn half(self) -> Self;
+}
+
+macro_rules! impl_half_f {
+ ($($t:ty)+) => {$(
+ impl Half for $t {
+ fn half(self) -> Self {
+ 0.5 * self
+ }
+ }
+ )+}
+}
+impl_half_f!(f32 f64);
+
+macro_rules! impl_half_i {
+ ($($t:ty)+) => {$(
+ impl Half for $t {
+ fn half(self) -> Self {
+ self >> 1
+ }
+ }
+ )+}
+}
+impl_half_i!(i8 i16 i32 i64 i128);
+
+impl<'a, T: Copy + Zero + Add + Mul<Output = T> + Sum + Half, const M: usize, const N: usize> Filter
+ for HbfDec<'a, T, M, N>
+{
+ type Item = T;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (2, 2 * (N - (2 * M - 1)))
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ 2 * M - 1
+ }
+
+ fn process_block<'b>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'b mut [Self::Item],
+ ) -> &'b mut [Self::Item] {
+ let x = x.unwrap_or(y);
+ debug_assert_eq!(x.len() & 1, 0);
+ let k = x.len() / 2;
+ // load input
+ for (xi, (even, odd)) in x.chunks_exact(2).zip(
+ self.even[M - 1..][..k]
+ .iter_mut()
+ .zip(self.odd.buf_mut()[..k].iter_mut()),
+ ) {
+ *even = xi[0];
+ *odd = xi[1];
+ }
+ // compute output
+ for (yi, (even, odd)) in y[..k]
+ .iter_mut()
+ .zip(self.even[..k].iter().zip(self.odd.get()))
+ {
+ *yi = (*even + odd).half();
+ }
+ // keep state
+ self.even.copy_within(k..k + M - 1, 0);
+ self.odd.keep_state(k);
+ &mut y[..k]
+ }
+}
+
+/// Half band interpolator (interpolation rate 2)
+///
+/// The effective number of DSP taps is 4*M - 1.
+///
+/// M: number of taps
+/// N: state size: N = 2*M - 1 + input.len()
+#[derive(Clone, Debug, Copy)]
+pub struct HbfInt<'a, T, const M: usize, const N: usize> {
+ fir: SymFir<'a, T, M, N>,
+}
+
+impl<'a, T: Copy + Zero + Add + Mul<Output = T> + Sum, const M: usize, const N: usize>
+ HbfInt<'a, T, M, N>
+{
+ /// Non-zero (odd) taps from oldest to one-before-center.
+ /// Normalized such that center tap is 1.
+ pub fn new(taps: &'a [T; M]) -> Self {
+ Self {
+ fir: SymFir::new(taps),
+ }
+ }
+
+ /// Obtain a mutable reference to the input items buffer space
+ pub fn buf_mut(&mut self) -> &mut [T] {
+ self.fir.buf_mut()
+ }
+}
+
+impl<'a, T: Copy + Zero + Add + Mul<Output = T> + Sum, const M: usize, const N: usize> Filter
+ for HbfInt<'a, T, M, N>
+{
+ type Item = T;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (2, 2 * (N - (2 * M - 1)))
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ 4 * M - 2
+ }
+
+ fn process_block<'b>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'b mut [Self::Item],
+ ) -> &'b mut [Self::Item] {
+ debug_assert_eq!(y.len() & 1, 0);
+ let k = y.len() / 2;
+ let x = x.unwrap_or(&y[..k]);
+ // load input
+ self.fir.buf_mut()[..k].copy_from_slice(x);
+ // compute output
+ for (yi, (even, &odd)) in y
+ .chunks_exact_mut(2)
+ .zip(self.fir.get().zip(self.fir.x[M..][..k].iter()))
+ {
+ // Choose the even item to be the interpolated one.
+ // The alternative would have the same response length
+ // but larger latency.
+ yi[0] = even; // interpolated
+ yi[1] = odd; // center tap: identity
+ }
+ // keep state
+ self.fir.keep_state(k);
+ y
+ }
+}
+
+/// Standard/optimal half-band filter cascade taps
+///
+/// * obtained with `2*signal.remez(4*n - 1, bands=(0, .5-df/2, .5+df/2, 1), desired=(1, 0), fs=2, grid_density=512)[:2*n:2]`
+/// * more than 98 dB stop band attenuation (>16 bit)
+/// * 0.4 pass band (relative to lowest sample rate)
+/// * less than 0.001 dB ripple
+/// * linear phase/flat group delay
+/// * rate change up to 2**5 = 32
+/// * lowest rate filter is at 0 index
+/// * use taps 0..n for 2**n interpolation/decimation
+#[allow(clippy::excessive_precision, clippy::type_complexity)]
+pub const HBF_TAPS_98: ([f32; 15], [f32; 6], [f32; 3], [f32; 3], [f32; 2]) = (
+ // n=15 coefficients (effective number of DSP taps 4*15-1 = 59), transition band width df=.2 fs
+ [
+ 7.02144012e-05,
+ -2.43279582e-04,
+ 6.35026936e-04,
+ -1.39782541e-03,
+ 2.74613582e-03,
+ -4.96403839e-03,
+ 8.41806912e-03,
+ -1.35827601e-02,
+ 2.11004053e-02,
+ -3.19267647e-02,
+ 4.77024289e-02,
+ -7.18014345e-02,
+ 1.12942004e-01,
+ -2.03279594e-01,
+ 6.33592923e-01,
+ ],
+ // 6, .47
+ [
+ -0.00086943,
+ 0.00577837,
+ -0.02201674,
+ 0.06357869,
+ -0.16627679,
+ 0.61979312,
+ ],
+ // 3, .754
+ [0.01414651, -0.10439639, 0.59026742],
+ // 3, .877
+ [0.01227974, -0.09930782, 0.58702834],
+ // 2, .94
+ [-0.06291796, 0.5629161],
+);
+
+/// * 140 dB stopband, 2 µdB passband ripple, limited by f32 dynamic range
+/// * otherwise like [`HBF_TAPS_98`].
+#[allow(clippy::excessive_precision, clippy::type_complexity)]
+pub const HBF_TAPS: ([f32; 23], [f32; 9], [f32; 5], [f32; 4], [f32; 3]) = (
+ [
+ 7.60376281e-07,
+ -3.77494189e-06,
+ 1.26458572e-05,
+ -3.43188258e-05,
+ 8.10687488e-05,
+ -1.72971471e-04,
+ 3.40845058e-04,
+ -6.29522838e-04,
+ 1.10128836e-03,
+ -1.83933298e-03,
+ 2.95124925e-03,
+ -4.57290979e-03,
+ 6.87374175e-03,
+ -1.00656254e-02,
+ 1.44199841e-02,
+ -2.03025099e-02,
+ 2.82462332e-02,
+ -3.91128510e-02,
+ 5.44795655e-02,
+ -7.77002648e-02,
+ 1.17523454e-01,
+ -2.06185386e-01,
+ 6.34588718e-01,
+ ],
+ [
+ 3.13788260e-05,
+ -2.90598691e-04,
+ 1.46009063e-03,
+ -5.22455620e-03,
+ 1.48913004e-02,
+ -3.62276956e-02,
+ 8.02305192e-02,
+ -1.80019379e-01,
+ 6.25149012e-01,
+ ],
+ [
+ 7.62032287e-04,
+ -7.64759816e-03,
+ 3.85545008e-02,
+ -1.39896080e-01,
+ 6.08227193e-01,
+ ],
+ [
+ -2.65761488e-03,
+ 2.49805823e-02,
+ -1.21497065e-01,
+ 5.99174082e-01,
+ ],
+ [1.18773514e-02, -9.81294960e-02, 5.86252153e-01],
+);
+
+/// Passband width in units of lowest sample rate
+pub const HBF_PASSBAND: f32 = 0.4;
+
+/// Max low-rate block size (HbfIntCascade input, HbfDecCascade output)
+pub const HBF_CASCADE_BLOCK: usize = 1 << 6;
+
+/// Half-band decimation filter cascade with optimal taps
+///
+/// See [HBF_TAPS].
+/// Only in-place processing is implemented.
+/// Supports rate changes of 1, 2, 4, 8, and 16.
+#[derive(Copy, Clone, Debug)]
+pub struct HbfDecCascade {
+ depth: usize,
+ stages: (
+ HbfDec<
+ 'static,
+ f32,
+ { HBF_TAPS.0.len() },
+ { 2 * HBF_TAPS.0.len() - 1 + HBF_CASCADE_BLOCK },
+ >,
+ HbfDec<
+ 'static,
+ f32,
+ { HBF_TAPS.1.len() },
+ { 2 * HBF_TAPS.1.len() - 1 + HBF_CASCADE_BLOCK * 2 },
+ >,
+ HbfDec<
+ 'static,
+ f32,
+ { HBF_TAPS.2.len() },
+ { 2 * HBF_TAPS.2.len() - 1 + HBF_CASCADE_BLOCK * 4 },
+ >,
+ HbfDec<
+ 'static,
+ f32,
+ { HBF_TAPS.3.len() },
+ { 2 * HBF_TAPS.3.len() - 1 + HBF_CASCADE_BLOCK * 8 },
+ >,
+ ),
+}
+
+impl Default for HbfDecCascade {
+ fn default() -> Self {
+ Self {
+ depth: 0,
+ stages: (
+ HbfDec::new(&HBF_TAPS.0),
+ HbfDec::new(&HBF_TAPS.1),
+ HbfDec::new(&HBF_TAPS.2),
+ HbfDec::new(&HBF_TAPS.3),
+ ),
+ }
+ }
+}
+
+impl HbfDecCascade {
+ /// Set cascade depth
+ ///
+ /// Sets the number of HBF filter stages to apply.
+ #[inline]
+ pub fn set_depth(&mut self, n: usize) {
+ assert!(n <= 4);
+ self.depth = n;
+ }
+
+ /// Cascade depth
+ ///
+ /// The number of HBF filter stages to apply.
+ #[inline]
+ pub fn depth(&self) -> usize {
+ self.depth
+ }
+}
+
+impl Filter for HbfDecCascade {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (
+ 1 << self.depth,
+ match self.depth {
+ 0 => usize::MAX,
+ 1 => self.stages.0.block_size().1,
+ 2 => self.stages.1.block_size().1,
+ 3 => self.stages.2.block_size().1,
+ _ => self.stages.3.block_size().1,
+ },
+ )
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ let mut n = 0;
+ if self.depth > 3 {
+ n = n / 2 + self.stages.3.response_length();
+ }
+ if self.depth > 2 {
+ n = n / 2 + self.stages.2.response_length();
+ }
+ if self.depth > 1 {
+ n = n / 2 + self.stages.1.response_length();
+ }
+ if self.depth > 0 {
+ n = n / 2 + self.stages.0.response_length();
+ }
+ n
+ }
+
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ mut y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item] {
+ if x.is_some() {
+ unimplemented!(); // TODO: pair of intermediate buffers
+ }
+ let n = y.len();
+
+ if self.depth > 3 {
+ y = self.stages.3.process_block(None, y);
+ }
+ if self.depth > 2 {
+ y = self.stages.2.process_block(None, y);
+ }
+ if self.depth > 1 {
+ y = self.stages.1.process_block(None, y);
+ }
+ if self.depth > 0 {
+ y = self.stages.0.process_block(None, y);
+ }
+ debug_assert_eq!(y.len(), n >> self.depth);
+ y
+ }
+}
+
+/// Half-band interpolation filter cascade with optimal taps.
+///
+/// This is a no_alloc version without trait objects.
+/// The price to pay is fixed and maximal memory usage independent
+/// of block size and cascade length.
+///
+/// See [HBF_TAPS].
+/// Only in-place processing is implemented.
+/// Supports rate changes of 1, 2, 4, 8, and 16.
+#[derive(Copy, Clone, Debug)]
+pub struct HbfIntCascade {
+ depth: usize,
+ stages: (
+ HbfInt<
+ 'static,
+ f32,
+ { HBF_TAPS.0.len() },
+ { 2 * HBF_TAPS.0.len() - 1 + HBF_CASCADE_BLOCK },
+ >,
+ HbfInt<
+ 'static,
+ f32,
+ { HBF_TAPS.1.len() },
+ { 2 * HBF_TAPS.1.len() - 1 + HBF_CASCADE_BLOCK * 2 },
+ >,
+ HbfInt<
+ 'static,
+ f32,
+ { HBF_TAPS.2.len() },
+ { 2 * HBF_TAPS.2.len() - 1 + HBF_CASCADE_BLOCK * 4 },
+ >,
+ HbfInt<
+ 'static,
+ f32,
+ { HBF_TAPS.3.len() },
+ { 2 * HBF_TAPS.3.len() - 1 + HBF_CASCADE_BLOCK * 8 },
+ >,
+ ),
+}
+
+impl Default for HbfIntCascade {
+ fn default() -> Self {
+ Self {
+ depth: 4,
+ stages: (
+ HbfInt::new(&HBF_TAPS.0),
+ HbfInt::new(&HBF_TAPS.1),
+ HbfInt::new(&HBF_TAPS.2),
+ HbfInt::new(&HBF_TAPS.3),
+ ),
+ }
+ }
+}
+
+impl HbfIntCascade {
+ /// Set cascade depth
+ ///
+ /// Sets the number of HBF filter stages to apply.
+ pub fn set_depth(&mut self, n: usize) {
+ assert!(n <= 4);
+ self.depth = n;
+ }
+
+ /// Cascade depth
+ ///
+ /// The number of HBF filter stages to apply.
+ pub fn depth(&self) -> usize {
+ self.depth
+ }
+}
+
+impl Filter for HbfIntCascade {
+ type Item = f32;
+
+ #[inline]
+ fn block_size(&self) -> (usize, usize) {
+ (
+ 1 << self.depth,
+ match self.depth {
+ 0 => usize::MAX,
+ 1 => self.stages.0.block_size().1,
+ 2 => self.stages.1.block_size().1,
+ 3 => self.stages.2.block_size().1,
+ _ => self.stages.3.block_size().1,
+ },
+ )
+ }
+
+ #[inline]
+ fn response_length(&self) -> usize {
+ let mut n = 0;
+ if self.depth > 0 {
+ n = 2 * n + self.stages.0.response_length();
+ }
+ if self.depth > 1 {
+ n = 2 * n + self.stages.1.response_length();
+ }
+ if self.depth > 2 {
+ n = 2 * n + self.stages.2.response_length();
+ }
+ if self.depth > 3 {
+ n = 2 * n + self.stages.3.response_length();
+ }
+ n
+ }
+
+ fn process_block<'a>(
+ &mut self,
+ x: Option<&[Self::Item]>,
+ y: &'a mut [Self::Item],
+ ) -> &'a mut [Self::Item] {
+ if x.is_some() {
+ unimplemented!(); // TODO: one intermediate buffer and `y`
+ }
+ // TODO: use buf_mut() and write directly into next filters' input buffer
+
+ let mut n = y.len() >> self.depth;
+ if self.depth > 0 {
+ n = self.stages.0.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 1 {
+ n = self.stages.1.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 2 {
+ n = self.stages.2.process_block(None, &mut y[..2 * n]).len();
+ }
+ if self.depth > 3 {
+ n = self.stages.3.process_block(None, &mut y[..2 * n]).len();
+ }
+ debug_assert_eq!(n, y.len());
+ &mut y[..n]
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use rustfft::{num_complex::Complex, FftPlanner};
+
+ #[test]
+ fn test() {
+ let mut h = HbfDec::<_, 1, 5>::new(&[0.5]);
+ assert_eq!(h.process_block(None, &mut []), &[]);
+
+ let mut x = [1.0; 8];
+ assert_eq!((2, x.len()), h.block_size());
+ let x = h.process_block(None, &mut x);
+ assert_eq!(x, [0.75, 1.0, 1.0, 1.0]);
+
+ let mut h = HbfDec::<_, { HBF_TAPS.3.len() }, 11>::new(&HBF_TAPS.3);
+ let mut x: Vec<_> = (0..8).map(|i| i as f32).collect();
+ assert_eq!((2, x.len()), h.block_size());
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x);
+ }
+
+ #[test]
+ fn decim() {
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ assert_eq!(
+ h.block_size(),
+ (1 << h.depth(), HBF_CASCADE_BLOCK << h.depth())
+ );
+ let mut x: Vec<_> = (0..2 << h.depth()).map(|i| i as f32).collect();
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x);
+ }
+
+ #[test]
+ fn response_length_dec() {
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ let mut y: Vec<f32> = (0..1 << 10).map(|_| rand::random()).collect();
+ h.process_block(None, &mut y);
+ let mut y = vec![0.0; 1 << 10];
+ let z = h.process_block(None, &mut y);
+ let n = h.response_length();
+ assert!(z[n - 1] != 0.0);
+ assert_eq!(z[n], 0.0);
+ }
+
+ #[test]
+ fn interp() {
+ let mut h = HbfIntCascade::default();
+ h.set_depth(4);
+ assert_eq!(
+ h.block_size(),
+ (1 << h.depth(), HBF_CASCADE_BLOCK << h.depth())
+ );
+ let k = h.block_size().0;
+ let r = h.response_length();
+ let mut x = vec![0.0; (r + 1 + k - 1) / k * k];
+ x[0] = 1.0;
+ let x = h.process_block(None, &mut x);
+ println!("{:?}", x); // interpolator impulse response
+ assert!(x[r] != 0.0);
+ assert_eq!(x[r + 1..], vec![0.0; x.len() - r - 1]);
+
+ let g = (1 << h.depth()) as f32;
+ let mut y = Vec::from_iter(x.iter().map(|&x| Complex {
+ re: x as f64 / g as f64,
+ im: 0.0,
+ }));
+ // pad
+ y.resize(5 << 10, Complex::default());
+ FftPlanner::new().plan_fft_forward(y.len()).process(&mut y);
+ // transfer function
+ let p = Vec::from_iter(y.iter().map(|y| 10.0 * y.norm_sqr().log10()));
+ let f = p.len() as f32 / g;
+ // pass band ripple
+ let p_pass = p[..(f * HBF_PASSBAND).floor() as _]
+ .iter()
+ .fold(0.0, |m, p| p.abs().max(m));
+ assert!(p_pass < 2e-6, "{p_pass}");
+ // stop band attenuation
+ let p_stop = p[(f * (1.0 - HBF_PASSBAND)).ceil() as _..p.len() / 2]
+ .iter()
+ .fold(-200.0, |m, p| p.max(m));
+ assert!(p_stop < -140.0, "{p_stop}");
+ }
+
+ /// small 32 block size, single stage, 3 mul (11 tap) decimator
+ /// 3.5 insn per input item, > 1 GS/s per core on Skylake
+ #[test]
+ #[ignore]
+ fn insn_dec() {
+ const N: usize = HBF_TAPS.4.len();
+ assert_eq!(N, 3);
+ let mut h = HbfDec::<_, N, { 2 * N - 1 + (1 << 4) }>::new(&HBF_TAPS.4);
+ let mut x = [9.0; 1 << 5];
+ for _ in 0..1 << 25 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ /// 1k block size, single stage, 23 mul (91 tap) decimator
+ /// 4.9 insn: > 1 GS/s
+ #[test]
+ #[ignore]
+ fn insn_dec2() {
+ const N: usize = HBF_TAPS.0.len();
+ assert_eq!(N, 23);
+ const M: usize = 1 << 10;
+ let mut h = HbfDec::<_, N, { 2 * N - 1 + M }>::new(&HBF_TAPS.0);
+ let mut x = [9.0; M];
+ for _ in 0..1 << 20 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ /// full block size full decimator cascade (depth 4, 1024 items per input block)
+ /// 4.1 insn: > 1 GS/s
+ #[test]
+ #[ignore]
+ fn insn_casc() {
+ let mut x = [9.0; 1 << 10];
+ let mut h = HbfDecCascade::default();
+ h.set_depth(4);
+ for _ in 0..1 << 20 {
+ h.process_block(None, &mut x);
+ }
+ }
+
+ // // sdr crate, setup like insn_dec2()
+ // // 187 insn
+ // #[test]
+ // #[ignore]
+ // fn insn_sdr() {
+ // use sdr::fir;
+ // const N: usize = HBF_TAPS.0.len();
+ // const M: usize = 1 << 10;
+ // let mut taps = [0.0f64; { 4 * N - 1 }];
+ // let (old, new) = taps.split_at_mut(2 * N - 1);
+ // for (tap, (old, new)) in HBF_TAPS.0.iter().zip(
+ // old.iter_mut()
+ // .step_by(2)
+ // .zip(new.iter_mut().rev().step_by(2)),
+ // ) {
+ // *old = (*tap * 0.5).into();
+ // *new = *old;
+ // }
+ // taps[2 * N - 1] = 0.5;
+ // let mut h = fir::FIR::new(&taps, 2, 1);
+ // let x = [9.0; M];
+ // // let mut h1 = HbfDec::<N, { 2 * N - 1 + M }>::new(&HBF_TAPS.0);
+ // // let mut y1 = [0.0; M / 2];
+ // for _ in 0..1 << 16 {
+ // let _y = h.process(&x);
+ // // h1.process_block(Some(&x), &mut y1);
+ // // assert_eq!(y1.len(), y.len());
+ // // assert!(y1.iter().zip(y.iter()).all(|(y1, y)| (y1 - y).abs() < 1e-6));
+ // }
+ // }
+
+ // // // futuredsp crate, setup like insn_dec2()
+ // // // 315 insn
+ // #[test]
+ // #[ignore]
+ // fn insn_futuredsp() {
+ // use futuredsp::{fir::PolyphaseResamplingFirKernel, UnaryKernel};
+ // const N: usize = HBF_TAPS.0.len();
+ // const M: usize = 1 << 10;
+ // let mut taps = [0.0f32; { 4 * N - 1 }];
+ // let (old, new) = taps.split_at_mut(2 * N - 1);
+ // for (tap, (old, new)) in HBF_TAPS.0.iter().zip(
+ // old.iter_mut()
+ // .step_by(2)
+ // .zip(new.iter_mut().rev().step_by(2)),
+ // ) {
+ // *old = *tap * 0.5;
+ // *new = *old;
+ // }
+ // taps[2 * N - 1] = 0.5;
+ // let x = [9.0f32; M];
+ // let mut y = [0.0f32; M];
+ // let fir = PolyphaseResamplingFirKernel::<_, _, _, _>::new(1, 2, taps);
+ // for _ in 0..1 << 14 {
+ // fir.work(&x, &mut y);
+ // }
+ // }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +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 +
use num_traits::{AsPrimitive, Float};
+use serde::{Deserialize, Serialize};
+
+use crate::Coefficient;
+
+/// Biquad IIR filter
+///
+/// A biquadratic IIR filter supports up to two zeros and two poles in the transfer function.
+/// It can be used to implement a wide range of responses to input signals.
+///
+/// The Biquad performs the following operation to compute a new output sample `y0` from a new
+/// input sample `x0` given its configuration and previous samples:
+///
+/// `y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)`
+///
+/// This implementation here saves storage and improves caching opportunities by decoupling
+/// filter configuration (coefficients, limits and offset) from filter state
+/// and thus supports both (a) sharing a single filter between multiple states ("channels") and (b)
+/// rapid switching of filters (tuning, transfer) for a given state without copying either
+/// state of configuration.
+///
+/// # Filter architecture
+///
+/// Direct Form 1 (DF1) and Direct Form 2 transposed (DF2T) are the only IIR filter
+/// structures with an (effective bin the case of TDF2) single summing junction
+/// this allows clamping of the output before feedback.
+///
+/// DF1 allows atomic coefficient change because only inputs and outputs are pipelined.
+/// The summing junctuion pipelining of TDF2 would require incremental
+/// coefficient changes and is thus less amenable to online tuning.
+///
+/// DF2T needs less state storage (2 instead of 4). This is in addition to the coefficient
+/// storage (5 plus 2 limits plus 1 offset)
+///
+/// DF2T is less efficient and accurate for fixed-point architectures as quantization
+/// happens at each intermediate summing junction in addition to the output quantization. This is
+/// especially true for common `i64 + i32 * i32 -> i64` MACC architectures.
+/// One could use wide state storage for fixed point DF2T but that would negate the storage
+/// and processing advantages.
+///
+/// # Coefficients
+///
+/// `ba: [T; 5] = [b0, b1, b2, a1, a2]` is the coefficients type.
+/// To represent the IIR coefficients, this contains the feed-forward
+/// coefficients `b0, b1, b2` followed by the feed-back coefficients
+/// `a1, a2`, all five normalized such that `a0 = 1`.
+///
+/// The summing junction of the filter also receives an offset `u`.
+///
+/// The filter applies clamping such that `min <= y <= max`.
+///
+/// See [`crate::iir::Filter`] and [`crate::iir::Pid`] for ways to generate coefficients.
+///
+/// # Fixed point
+///
+/// Coefficient scaling (see [`Coefficient`]) is fixed and optimized such that -2 is exactly
+/// representable. This is tailored to low-passes, PID, II etc, where the integration rule is
+/// [1, -2, 1].
+///
+/// There are two guard bits in the accumulator before clamping/limiting.
+/// While this isn't enough to cover the worst case accumulator, it does catch many real world
+/// overflow cases.
+///
+/// # State
+///
+/// To represent the IIR state (input and output memory) during [`Biquad::update()`]
+/// the DF1 state contains the two previous inputs and output `[x1, x2, y1, y2]`
+/// concatenated. Lower indices correspond to more recent samples.
+///
+/// In the DF2T case the state contains `[b1*x1 + b2*x2 - a1*y1 - a2*y2, b2*x1 - a2*y1]`
+///
+/// In the DF1 case with first order noise shaping, the state contains `[x1, x2, y1, y2, e1]`
+/// where `e0` is the accumulated quantization error.
+///
+/// # PID controller
+///
+/// The IIR coefficients can be mapped to other transfer function
+/// representations, for example PID controllers as described in
+/// <https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw> and
+/// <https://arxiv.org/abs/1508.06319>.
+///
+/// Using a Biquad as a template for a PID controller 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 summing junction (in output units).
+/// 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 coefficients and offset).
+/// Therefore it can trivially implement bump-less transfer between any
+/// coefficients/offset sets.
+/// * Cascading multiple IIR filters allows stable and robust
+/// implementation of transfer functions beyond bequadratic terms.
+#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
+pub struct Biquad<T> {
+ ba: [T; 5],
+ u: T,
+ min: T,
+ max: T,
+}
+
+impl<T: Coefficient> Default for Biquad<T> {
+ fn default() -> Self {
+ Self {
+ ba: [T::ZERO; 5],
+ u: T::ZERO,
+ min: T::MIN,
+ max: T::MAX,
+ }
+ }
+}
+
+impl<T: Coefficient> From<[T; 5]> for Biquad<T> {
+ fn from(ba: [T; 5]) -> Self {
+ Self {
+ ba,
+ ..Default::default()
+ }
+ }
+}
+
+impl<T, C> From<&[C; 6]> for Biquad<T>
+where
+ T: Coefficient + AsPrimitive<C>,
+ C: Float + AsPrimitive<T>,
+{
+ fn from(ba: &[C; 6]) -> Self {
+ let ia0 = C::one() / ba[3];
+ Self::from([
+ T::quantize(ba[0] * ia0),
+ T::quantize(ba[1] * ia0),
+ T::quantize(ba[2] * ia0),
+ // b[3]: a0*ia0
+ T::quantize(ba[4] * ia0),
+ T::quantize(ba[5] * ia0),
+ ])
+ }
+}
+
+impl<T, C> From<&Biquad<T>> for [C; 6]
+where
+ T: Coefficient + AsPrimitive<C>,
+ C: 'static + Copy,
+{
+ fn from(value: &Biquad<T>) -> Self {
+ let ba = value.ba();
+ [
+ ba[0].as_(),
+ ba[1].as_(),
+ ba[2].as_(),
+ T::ONE.as_(),
+ ba[3].as_(),
+ ba[4].as_(),
+ ]
+ }
+}
+
+impl<T: Coefficient> Biquad<T> {
+ /// A "hold" filter that ingests input and maintains output
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut xy = [0.0, 1.0, 2.0, 3.0];
+ /// let x0 = 7.0;
+ /// let y0 = Biquad::HOLD.update(&mut xy, x0);
+ /// assert_eq!(y0, 2.0);
+ /// assert_eq!(xy, [x0, 0.0, y0, y0]);
+ /// ```
+ pub const HOLD: Self = Self {
+ ba: [T::ZERO, T::ZERO, T::ZERO, T::NEG_ONE, T::ZERO],
+ u: T::ZERO,
+ min: T::MIN,
+ max: T::MAX,
+ };
+
+ /// A unity gain filter
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let x0 = 3.0;
+ /// let y0 = Biquad::IDENTITY.update(&mut [0.0; 4], x0);
+ /// assert_eq!(y0, x0);
+ /// ```
+ pub const IDENTITY: Self = Self::proportional(T::ONE);
+
+ /// A filter with the given proportional gain at all frequencies
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let x0 = 2.0;
+ /// let k = 5.0;
+ /// let y0 = Biquad::proportional(k).update(&mut [0.0; 4], x0);
+ /// assert_eq!(y0, x0 * k);
+ /// ```
+ pub const fn proportional(k: T) -> Self {
+ Self {
+ ba: [k, T::ZERO, T::ZERO, T::ZERO, T::ZERO],
+ u: T::ZERO,
+ min: T::MIN,
+ max: T::MAX,
+ }
+ }
+
+ /// Filter coefficients
+ ///
+ /// IIR filter tap gains (`ba`) are an array `[b0, b1, b2, a1, a2]` such that
+ /// [`Biquad::update(&mut xy, x0)`] returns
+ /// `y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)`.
+ ///
+ /// ```
+ /// # use idsp::Coefficient;
+ /// # use idsp::iir::*;
+ /// assert_eq!(Biquad::<i32>::IDENTITY.ba()[0], <i32 as Coefficient>::ONE);
+ /// assert_eq!(Biquad::<i32>::HOLD.ba()[3], -<i32 as Coefficient>::ONE);
+ /// ```
+ pub fn ba(&self) -> &[T; 5] {
+ &self.ba
+ }
+
+ /// Mutable reference to the filter coefficients.
+ ///
+ /// See [`Biquad::ba()`].
+ ///
+ /// ```
+ /// # use idsp::Coefficient;
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::default();
+ /// i.ba_mut()[0] = <i32 as Coefficient>::ONE;
+ /// assert_eq!(i, Biquad::IDENTITY);
+ /// ```
+ pub fn ba_mut(&mut self) -> &mut [T; 5] {
+ &mut self.ba
+ }
+
+ /// Summing junction offset
+ ///
+ /// This offset is applied to the output `y0` summing junction
+ /// on top of the feed-forward (`b`) and feed-back (`a`) terms.
+ /// The feedback samples are taken at the summing junction and
+ /// thus also include (and feed back) this offset.
+ pub fn u(&self) -> T {
+ self.u
+ }
+
+ /// Set the summing junction offset
+ ///
+ /// See [`Biquad::u()`].
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::default();
+ /// i.set_u(5);
+ /// assert_eq!(i.update(&mut [0; 4], 0), 5);
+ /// ```
+ pub fn set_u(&mut self, u: T) {
+ self.u = u;
+ }
+
+ /// Lower output limit
+ ///
+ /// Guaranteed minimum output value.
+ /// The value is inclusive.
+ /// The clamping also cleanly affects the feedback terms.
+ ///
+ /// For fixed point types, during the comparison,
+ /// the lowest two bits of value and limit are truncated.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// assert_eq!(Biquad::<i32>::default().min(), i32::MIN);
+ /// ```
+ pub fn min(&self) -> T {
+ self.min
+ }
+
+ /// Set the lower output limit
+ ///
+ /// See [`Biquad::min()`].
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::default();
+ /// i.set_min(4);
+ /// assert_eq!(i.update(&mut [0; 4], 0), 4);
+ /// ```
+ pub fn set_min(&mut self, min: T) {
+ self.min = min;
+ }
+
+ /// Upper output limit
+ ///
+ /// Guaranteed maximum output value.
+ /// The value is inclusive.
+ /// The clamping also cleanly affects the feedback terms.
+ ///
+ /// For fixed point types, during the comparison,
+ /// the lowest two bits of value and limit are truncated.
+ /// The behavior is as if those two bits were 0 in the case
+ /// of `min` and one in the case of `max`.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// assert_eq!(Biquad::<i32>::default().max(), i32::MAX);
+ /// ```
+ pub fn max(&self) -> T {
+ self.max
+ }
+
+ /// Set the upper output limit
+ ///
+ /// See [`Biquad::max()`].
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::default();
+ /// i.set_max(-5);
+ /// assert_eq!(i.update(&mut [0; 4], 0), -5);
+ /// ```
+ pub fn set_max(&mut self, max: T) {
+ self.max = max;
+ }
+
+ /// Compute the overall (DC/proportional feed-forward) gain.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// assert_eq!(Biquad::proportional(3.0).forward_gain(), 3.0);
+ /// ```
+ ///
+ /// # Returns
+ /// The sum of the `b` feed-forward coefficients.
+ pub fn forward_gain(&self) -> T {
+ self.ba[0] + self.ba[1] + self.ba[2]
+ }
+
+ /// Compute input-referred (`x`) offset.
+ ///
+ /// ```
+ /// # use idsp::Coefficient;
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::proportional(3);
+ /// i.set_u(3);
+ /// assert_eq!(i.input_offset(), <i32 as Coefficient>::ONE);
+ /// ```
+ pub fn input_offset(&self) -> T {
+ self.u.div_scaled(self.forward_gain())
+ }
+
+ /// Convert input (`x`) offset to equivalent summing junction offset (`u`) and apply.
+ ///
+ /// In the case of a "PID" controller the response behavior of the controller
+ /// to the offset is "stabilizing", and not "tracking": its frequency response
+ /// is exclusively according to the lowest non-zero [`crate::iir::Action`] gain.
+ /// There is no high order ("faster") response as would be the case for a "tracking"
+ /// controller.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::proportional(3.0);
+ /// i.set_input_offset(2.0);
+ /// let x0 = 0.5;
+ /// let y0 = i.update(&mut [0.0; 4], x0);
+ /// assert_eq!(y0, (x0 + i.input_offset()) * i.forward_gain());
+ /// ```
+ ///
+ /// ```
+ /// # use idsp::Coefficient;
+ /// # use idsp::iir::*;
+ /// let mut i = Biquad::proportional(-<i32 as Coefficient>::ONE);
+ /// i.set_input_offset(1);
+ /// assert_eq!(i.u(), -1);
+ /// ```
+ ///
+ /// # Arguments
+ /// * `offset`: Input (`x`) offset.
+ pub fn set_input_offset(&mut self, offset: T) {
+ self.u = offset.mul_scaled(self.forward_gain());
+ }
+
+ /// Direct Form 1 Update
+ ///
+ /// Ingest a new input value into the filter, update the filter state, and
+ /// return the new output. Only the state `xy` is modified.
+ ///
+ /// ## `N=4` Direct Form 1
+ ///
+ /// `xy` contains:
+ /// * On entry: `[x1, x2, y1, y2]`
+ /// * On exit: `[x0, x1, y0, y1]`
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut xy = [0.0, 1.0, 2.0, 3.0];
+ /// let x0 = 4.0;
+ /// let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+ /// assert_eq!(y0, x0);
+ /// assert_eq!(xy, [x0, 0.0, y0, 2.0]);
+ /// ```
+ ///
+ /// ## `N=5` Direct Form 1 with first order noise shaping
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut xy = [1, 2, 3, 4, 5];
+ /// let x0 = 6;
+ /// let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+ /// assert_eq!(y0, x0);
+ /// assert_eq!(xy, [x0, 1, y0, 3, 5]);
+ /// ```
+ ///
+ /// `xy` contains:
+ /// * On entry: `[x1, x2, y1, y2, e1]`
+ /// * On exit: `[x0, x1, y0, y1, e0]`
+ ///
+ /// Note: This is only useful for fixed point filters.
+ ///
+ /// ## `N=2` Direct Form 2 transposed
+ ///
+ /// Note: This is only useful for floating point filters.
+ /// Don't use this for fixed point: Quantization happens at each state store operation.
+ /// Ideally the state would be `[T::ACCU; 2]` but then for fixed point it would use equal amount
+ /// of storage compared to DF1 for no gain in performance and loss in functionality.
+ /// There are also no guard bits here.
+ ///
+ /// `xy` contains:
+ /// * On entry: `[b1*x1 + b2*x2 - a1*y1 - a2*y2, b2*x1 - a2*y1]`
+ /// * On exit: `[b1*x0 + b2*x1 - a1*y0 - a2*y1, b2*x0 - a2*y0]`
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let mut xy = [0.0, 1.0];
+ /// let x0 = 3.0;
+ /// let y0 = Biquad::IDENTITY.update(&mut xy, x0);
+ /// assert_eq!(y0, x0);
+ /// assert_eq!(xy, [1.0, 0.0]);
+ /// ```
+ ///
+ /// # Arguments
+ /// * `xy` - Current filter state.
+ /// * `x0` - New input.
+ ///
+ /// # Returns
+ /// The new output `y0 = clamp(b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 + u, min, max)`
+ pub fn update<const N: usize>(&self, xy: &mut [T; N], x0: T) -> T {
+ match N {
+ // DF1
+ 4 => {
+ let s = self.ba[0].as_() * x0.as_()
+ + self.ba[1].as_() * xy[0].as_()
+ + self.ba[2].as_() * xy[1].as_()
+ - self.ba[3].as_() * xy[2].as_()
+ - self.ba[4].as_() * xy[3].as_();
+ let (y0, _) = self.u.macc(s, self.min, self.max, T::ZERO);
+ xy[1] = xy[0];
+ xy[0] = x0;
+ xy[3] = xy[2];
+ xy[2] = y0;
+ y0
+ }
+ // DF1 with noise shaping for fixed point
+ 5 => {
+ let s = self.ba[0].as_() * x0.as_()
+ + self.ba[1].as_() * xy[0].as_()
+ + self.ba[2].as_() * xy[1].as_()
+ - self.ba[3].as_() * xy[2].as_()
+ - self.ba[4].as_() * xy[3].as_();
+ let (y0, e0) = self.u.macc(s, self.min, self.max, xy[4]);
+ xy[4] = e0;
+ xy[1] = xy[0];
+ xy[0] = x0;
+ xy[3] = xy[2];
+ xy[2] = y0;
+ y0
+ }
+ // DF2T for floating point
+ 2 => {
+ let y0 = (xy[0] + self.ba[0].mul_scaled(x0)).clip(self.min, self.max);
+ xy[0] = xy[1] + self.ba[1].mul_scaled(x0) - self.ba[3].mul_scaled(y0);
+ xy[1] = self.u + self.ba[2].mul_scaled(x0) - self.ba[4].mul_scaled(y0);
+ y0
+ }
+ _ => unimplemented!(),
+ }
+ }
+}
+
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 +
use num_traits::{AsPrimitive, Float, FloatConst};
+use serde::{Deserialize, Serialize};
+
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
+enum Shape<T> {
+ /// Inverse Q, sqrt(2) for critical
+ InverseQ(T),
+ /// Relative bandwidth in octaves
+ Bandwidth(T),
+ /// Slope steepnes, 1 for critical
+ Slope(T),
+}
+
+impl<T: Float + FloatConst> Default for Shape<T> {
+ fn default() -> Self {
+ Self::InverseQ(T::SQRT_2())
+ }
+}
+
+/// Standard audio biquad filter builder
+///
+/// <https://www.w3.org/TR/audio-eq-cookbook/>
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
+pub struct Filter<T> {
+ /// Angular critical frequency (in units of sampling frequency)
+ /// Corner frequency, or 3dB cutoff frequency,
+ w0: T,
+ /// Passband gain
+ gain: T,
+ /// Shelf gain (only for peaking, lowshelf, highshelf)
+ /// Relative to passband gain
+ shelf: T,
+ /// Inverse Q
+ shape: Shape<T>,
+}
+
+impl<T: Float + FloatConst> Default for Filter<T> {
+ fn default() -> Self {
+ Self {
+ w0: T::zero(),
+ gain: T::one(),
+ shape: Shape::default(),
+ shelf: T::one(),
+ }
+ }
+}
+
+impl<T> Filter<T>
+where
+ T: 'static + Float + FloatConst,
+ f32: AsPrimitive<T>,
+{
+ /// Set crititcal frequency from absolute units.
+ ///
+ /// # Arguments
+ /// * `critical_frequency`: "Relevant" or "corner" or "center" frequency
+ /// in the same units as `sample_frequency`
+ /// * `sample_frequency`: The sample frequency in the same units as `critical_frequency`.
+ /// E.g. both in SI Hertz or `rad/s`.
+ pub fn frequency(&mut self, critical_frequency: T, sample_frequency: T) -> &mut Self {
+ self.critical_frequency(critical_frequency / sample_frequency)
+ }
+
+ /// Set relative critical frequency
+ ///
+ /// # Arguments
+ /// * `f0`: Relative critical frequency in units of the sample frequency.
+ /// Must be `0 <= f0 <= 0.5`.
+ pub fn critical_frequency(&mut self, f0: T) -> &mut Self {
+ self.angular_critical_frequency(T::TAU() * f0)
+ }
+
+ /// Set relative critical angular frequency
+ ///
+ /// # Arguments
+ /// * `w0`: Relative critical angular frequency.
+ /// Must be `0 <= w0 <= π`. Defaults to `0.0`.
+ pub fn angular_critical_frequency(&mut self, w0: T) -> &mut Self {
+ self.w0 = w0;
+ self
+ }
+
+ /// Set reference gain
+ ///
+ /// # Arguments
+ /// * `k`: Linear reference gain. Defaults to `1.0`.
+ pub fn gain(&mut self, k: T) -> &mut Self {
+ self.gain = k;
+ self
+ }
+
+ /// Set reference gain in dB
+ ///
+ /// # Arguments
+ /// * `k_db`: Reference gain in dB. Defaults to `0.0`.
+ pub fn gain_db(&mut self, k_db: T) -> &mut Self {
+ self.gain(10.0.as_().powf(k_db / 20.0.as_()))
+ }
+
+ /// Set linear shelf gain
+ ///
+ /// Used only for `peaking`, `highshelf`, `lowshelf` filters.
+ ///
+ /// # Arguments
+ /// * `a`: Linear shelf gain. Defaults to `1.0`.
+ pub fn shelf(&mut self, a: T) -> &mut Self {
+ self.shelf = a;
+ self
+ }
+
+ /// Set shelf gain in dB
+ ///
+ /// Used only for `peaking`, `highshelf`, `lowshelf` filters.
+ ///
+ /// # Arguments
+ /// * `a_db`: Linear shelf gain. Defaults to `0.0`.
+ pub fn shelf_db(&mut self, a_db: T) -> &mut Self {
+ self.shelf(10.0.as_().powf(a_db / 20.0.as_()))
+ }
+
+ /// Set inverse Q parameter of the filter
+ ///
+ /// The inverse "steepness"/"narrowness" of the filter transition.
+ /// Defaults `sqrt(2)` which is as steep as possible without overshoot.
+ ///
+ /// # Arguments
+ /// * `qi`: Inverse Q parameter.
+ pub fn inverse_q(&mut self, qi: T) -> &mut Self {
+ self.shape = Shape::InverseQ(qi);
+ self
+ }
+
+ /// Set Q parameter of the filter
+ ///
+ /// The "steepness"/"narrowness" of the filter transition.
+ /// Defaults `1/sqrt(2)` which is as steep as possible without overshoot.
+ ///
+ /// This affects the same parameter as `bandwidth()` and `shelf_slope()`.
+ /// Use only one of them.
+ ///
+ /// # Arguments
+ /// * `q`: Q parameter.
+ pub fn q(&mut self, q: T) -> &mut Self {
+ self.inverse_q(T::one() / q)
+ }
+
+ /// Set the relative bandwidth
+ ///
+ /// This affects the same parameter as `inverse_q()` and `shelf_slope()`.
+ /// Use only one of them.
+ ///
+ /// # Arguments
+ /// * `bw`: Bandwidth in octaves
+ pub fn bandwidth(&mut self, bw: T) -> &mut Self {
+ self.shape = Shape::Bandwidth(bw);
+ self
+ }
+
+ /// Set the shelf slope.
+ ///
+ /// This affects the same parameter as `inverse_q()` and `bandwidth()`.
+ /// Use only one of them.
+ ///
+ /// # Arguments
+ /// * `s`: Shelf slope. A slope of `1.0` is maximally steep without overshoot.
+ pub fn shelf_slope(&mut self, s: T) -> &mut Self {
+ self.shape = Shape::Slope(s);
+ self
+ }
+
+ /// Get inverse Q
+ fn qi(&self) -> T {
+ match self.shape {
+ Shape::InverseQ(qi) => qi,
+ Shape::Bandwidth(bw) => {
+ 2.0.as_() * (T::LN_2() / 2.0.as_() * bw * self.w0 / self.w0.sin()).sinh()
+ }
+ Shape::Slope(s) => {
+ ((self.gain + T::one() / self.gain) * (T::one() / s - T::one()) + 2.0.as_()).sqrt()
+ }
+ }
+ }
+
+ /// Get (cos(w0), alpha=sin(w0)/(2*q))
+ fn fcos_alpha(&self) -> (T, T) {
+ let (fsin, fcos) = self.w0.sin_cos();
+ (fcos, 0.5.as_() * fsin * self.qi())
+ }
+
+ /// Low pass filter
+ ///
+ /// Builds second order biquad low pass filter coefficients.
+ ///
+ /// ```
+ /// use idsp::iir::*;
+ /// let ba = Filter::default()
+ /// .critical_frequency(0.1)
+ /// .gain(1000.0)
+ /// .lowpass();
+ /// let iir = Biquad::<i32>::from(&ba);
+ /// let mut xy = [0; 4];
+ /// let x = vec![3, -4, 5, 7, -3, 2];
+ /// let y: Vec<_> = x.iter().map(|x0| iir.update(&mut xy, *x0)).collect();
+ /// assert_eq!(y, [5, 3, 9, 25, 42, 49]);
+ /// ```
+ pub fn lowpass(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let b = self.gain * 0.5.as_() * (T::one() - fcos);
+ [
+ b,
+ (2.0).as_() * b,
+ b,
+ T::one() + alpha,
+ (-2.0).as_() * fcos,
+ T::one() - alpha,
+ ]
+ }
+
+ /// High pass filter
+ ///
+ /// Builds second order biquad high pass filter coefficients.
+ ///
+ /// ```
+ /// use idsp::iir::*;
+ /// let ba = Filter::default()
+ /// .critical_frequency(0.1)
+ /// .gain(1000.0)
+ /// .highpass();
+ /// let iir = Biquad::<i32>::from(&ba);
+ /// let mut xy = [0; 4];
+ /// let x = vec![3, -4, 5, 7, -3, 2];
+ /// let y: Vec<_> = x.iter().map(|x0| iir.update(&mut xy, *x0)).collect();
+ /// assert_eq!(y, [5, -9, 11, 12, -1, 17]);
+ /// ```
+ pub fn highpass(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let b = self.gain * 0.5.as_() * (T::one() + fcos);
+ [
+ b,
+ (-2.0).as_() * b,
+ b,
+ T::one() + alpha,
+ (-2.0).as_() * fcos,
+ T::one() - alpha,
+ ]
+ }
+
+ /// Band pass
+ ///
+ /// ```
+ /// use idsp::iir::*;
+ /// let ba = Filter::default()
+ /// .frequency(1000.0, 48e3)
+ /// .q(5.0)
+ /// .gain_db(3.0)
+ /// .bandpass();
+ /// println!("{ba:?}");
+ /// ```
+ pub fn bandpass(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let b = self.gain * alpha;
+ [
+ b,
+ T::zero(),
+ -b,
+ T::one() + alpha,
+ (-2.0).as_() * fcos,
+ T::one() - alpha,
+ ]
+ }
+
+ /// A notch filter
+ ///
+ /// Has zero gain at the critical frequency.
+ pub fn notch(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let f2 = (-2.0).as_() * fcos;
+ [
+ self.gain,
+ f2 * self.gain,
+ self.gain,
+ T::one() + alpha,
+ f2,
+ T::one() - alpha,
+ ]
+ }
+
+ /// An allpass filter
+ ///
+ /// Has constant `gain` at all frequency but a variable phase shift.
+ pub fn allpass(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let f2 = (-2.0).as_() * fcos;
+ [
+ (T::one() - alpha) * self.gain,
+ f2 * self.gain,
+ (T::one() + alpha) * self.gain,
+ T::one() + alpha,
+ f2,
+ T::one() - alpha,
+ ]
+ }
+
+ /// A peaking/dip filter
+ ///
+ /// Has `gain*shelf_gain` at critical frequency and `gain` elsewhere.
+ pub fn peaking(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let s = self.shelf.sqrt();
+ let f2 = (-2.0).as_() * fcos;
+ [
+ (T::one() + alpha * s) * self.gain,
+ f2 * self.gain,
+ (T::one() - alpha * s) * self.gain,
+ T::one() + alpha / s,
+ f2,
+ T::one() - alpha / s,
+ ]
+ }
+
+ /// Low shelf
+ ///
+ /// Approaches `gain*shelf_gain` below critical frequency and `gain` above.
+ ///
+ /// ```
+ /// use idsp::iir::*;
+ /// let ba = Filter::default()
+ /// .frequency(1000.0, 48e3)
+ /// .shelf_slope(2.0)
+ /// .shelf_db(20.0)
+ /// .lowshelf();
+ /// println!("{ba:?}");
+ /// ```
+ pub fn lowshelf(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let s = self.shelf.sqrt();
+ let tsa = 2.0.as_() * s.sqrt() * alpha;
+ let sp1 = s + T::one();
+ let sm1 = s - T::one();
+ [
+ s * self.gain * (sp1 - sm1 * fcos + tsa),
+ 2.0.as_() * s * self.gain * (sm1 - sp1 * fcos),
+ s * self.gain * (sp1 - sm1 * fcos - tsa),
+ sp1 + sm1 * fcos + tsa,
+ (-2.0).as_() * (sm1 + sp1 * fcos),
+ sp1 + sm1 * fcos - tsa,
+ ]
+ }
+
+ /// Low shelf
+ ///
+ /// Approaches `gain*shelf_gain` above critical frequency and `gain` below.
+ pub fn highshelf(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let s = self.shelf.sqrt();
+ let tsa = 2.0.as_() * s.sqrt() * alpha;
+ let sp1 = s + T::one();
+ let sm1 = s - T::one();
+ [
+ s * self.gain * (sp1 + sm1 * fcos + tsa),
+ (-2.0).as_() * s * self.gain * (sm1 + sp1 * fcos),
+ s * self.gain * (sp1 + sm1 * fcos - tsa),
+ sp1 - sm1 * fcos + tsa,
+ 2.0.as_() * (sm1 - sp1 * fcos),
+ sp1 - sm1 * fcos - tsa,
+ ]
+ }
+
+ /// I/HO
+ ///
+ /// Notch, integrating below, flat `shelf_gain` above
+ pub fn iho(&self) -> [T; 6] {
+ let (fcos, alpha) = self.fcos_alpha();
+ let fsin = 0.5.as_() * self.w0.sin();
+ let a = (T::one() + fcos) / (2.0.as_() * self.shelf);
+ [
+ self.gain * (T::one() + alpha),
+ (-2.0).as_() * self.gain * fcos,
+ self.gain * (T::one() - alpha),
+ a + fsin,
+ (-2.0).as_() * a,
+ a - fsin,
+ ]
+ }
+}
+
+// TODO
+// SOS cascades:
+// butterworth
+// elliptic
+// chebychev1/2
+// bessel
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use core::f64;
+ use num_complex::Complex64;
+
+ use crate::iir::*;
+
+ #[test]
+ #[ignore]
+ fn lowpass_noise_shaping() {
+ let ba = Biquad::<i32>::from(
+ &Filter::default()
+ .critical_frequency(1e-5f64)
+ .gain(1e3)
+ .lowpass(),
+ );
+ println!("{:?}", ba);
+ let mut xy = [0; 5];
+ for _ in 0..(1 << 24) {
+ ba.update(&mut xy, 1);
+ }
+ for _ in 0..10 {
+ ba.update(&mut xy, 1);
+ println!("{xy:?}");
+ }
+ }
+
+ fn polyval(p: &[f64], x: Complex64) -> Complex64 {
+ p.iter()
+ .fold(
+ (Complex64::default(), Complex64::new(1.0, 0.0)),
+ |(a, xi), pi| (a + xi * *pi, xi * x),
+ )
+ .0
+ }
+
+ fn freqz(b: &[f64], a: &[f64], f: f64) -> Complex64 {
+ let z = Complex64::new(0.0, -f64::consts::TAU * f).exp();
+ polyval(b, z) / polyval(a, z)
+ }
+
+ #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+ enum Tol {
+ GainDb(f64, f64),
+ GainBelowDb(f64),
+ }
+ impl Tol {
+ fn check(&self, h: Complex64) -> bool {
+ let g = 10.0 * h.norm_sqr().log10();
+ match self {
+ Self::GainDb(want, tol) => (g - want).abs() <= *tol,
+ Self::GainBelowDb(want) => g <= *want,
+ }
+ }
+ }
+
+ fn check_freqz(f: f64, g: Tol, ba: &[f64; 6]) {
+ let h = freqz(&ba[..3], &ba[3..], f);
+ let hp = h.to_polar();
+ assert!(
+ g.check(h),
+ "freq {f}: response {h}={hp:?} does not meet {g:?}"
+ );
+ }
+
+ fn check_transfer(ba: &[f64; 6], fg: &[(f64, Tol)]) {
+ println!("{ba:?}");
+
+ for (f, g) in fg {
+ check_freqz(*f, *g, ba);
+ }
+
+ // Quantize and back
+ let bai = (&Biquad::<i32>::from(ba)).into();
+ println!("{bai:?}");
+
+ for (f, g) in fg {
+ check_freqz(*f, *g, &bai);
+ }
+ }
+
+ #[test]
+ fn lowpass() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.01)
+ .gain_db(20.0)
+ .lowpass(),
+ &[
+ (1e-3, Tol::GainDb(20.0, 0.01)),
+ (0.01, Tol::GainDb(17.0, 0.02)),
+ (4e-1, Tol::GainBelowDb(-40.0)),
+ ],
+ );
+ }
+
+ #[test]
+ fn highpass() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.1)
+ .gain_db(-2.0)
+ .highpass(),
+ &[
+ (1e-3, Tol::GainBelowDb(-40.0)),
+ (0.1, Tol::GainDb(-5.0, 0.02)),
+ (4e-1, Tol::GainDb(-2.0, 0.01)),
+ ],
+ );
+ }
+
+ #[test]
+ fn bandpass() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .bandwidth(2.0)
+ .gain_db(3.0)
+ .bandpass(),
+ &[
+ (1e-4, Tol::GainBelowDb(-35.0)),
+ (0.01, Tol::GainDb(0.0, 0.02)),
+ (0.02, Tol::GainDb(3.0, 0.01)),
+ (0.04, Tol::GainDb(0.0, 0.04)),
+ (4e-1, Tol::GainBelowDb(-25.0)),
+ ],
+ );
+ }
+
+ #[test]
+ fn allpass() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .gain_db(-10.0)
+ .allpass(),
+ &[
+ (1e-4, Tol::GainDb(-10.0, 0.01)),
+ (0.01, Tol::GainDb(-10.0, 0.01)),
+ (0.02, Tol::GainDb(-10.0, 0.01)),
+ (0.04, Tol::GainDb(-10.0, 0.01)),
+ (4e-1, Tol::GainDb(-10.0, 0.01)),
+ ],
+ );
+ }
+
+ #[test]
+ fn notch() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .bandwidth(2.0)
+ .notch(),
+ &[
+ (1e-4, Tol::GainDb(0.0, 0.01)),
+ (0.01, Tol::GainDb(-3.0, 0.02)),
+ (0.02, Tol::GainBelowDb(-140.0)),
+ (0.04, Tol::GainDb(-3.0, 0.02)),
+ (4e-1, Tol::GainDb(0.0, 0.01)),
+ ],
+ );
+ }
+
+ #[test]
+ fn peaking() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .bandwidth(2.0)
+ .gain_db(-10.0)
+ .shelf_db(20.0)
+ .peaking(),
+ &[
+ (1e-4, Tol::GainDb(-10.0, 0.01)),
+ (0.01, Tol::GainDb(0.0, 0.04)),
+ (0.02, Tol::GainDb(10.0, 0.01)),
+ (0.04, Tol::GainDb(0.0, 0.04)),
+ (4e-1, Tol::GainDb(-10.0, 0.05)),
+ ],
+ );
+ }
+
+ #[test]
+ fn highshelf() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .gain_db(-10.0)
+ .shelf_db(-20.0)
+ .highshelf(),
+ &[
+ (1e-6, Tol::GainDb(-10.0, 0.01)),
+ (1e-4, Tol::GainDb(-10.0, 0.01)),
+ (0.02, Tol::GainDb(-20.0, 0.01)),
+ (4e-1, Tol::GainDb(-30.0, 0.01)),
+ ],
+ );
+ }
+
+ #[test]
+ fn lowshelf() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.02)
+ .gain_db(-10.0)
+ .shelf_db(-20.0)
+ .lowshelf(),
+ &[
+ (1e-6, Tol::GainDb(-30.0, 0.01)),
+ (1e-4, Tol::GainDb(-30.0, 0.01)),
+ (0.02, Tol::GainDb(-20.0, 0.01)),
+ (4e-1, Tol::GainDb(-10.0, 0.01)),
+ ],
+ );
+ }
+
+ #[test]
+ fn iho() {
+ check_transfer(
+ &Filter::default()
+ .critical_frequency(0.01)
+ .gain_db(-20.0)
+ .shelf_db(10.0)
+ .q(10.)
+ .iho(),
+ &[
+ (1e-5, Tol::GainDb(40.0, 0.01)),
+ (0.01, Tol::GainBelowDb(-40.0)),
+ (4.99e-1, Tol::GainDb(-10.0, 0.01)),
+ ],
+ );
+ }
+}
+
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 +
use num_traits::{AsPrimitive, Float};
+use serde::{Deserialize, Serialize};
+
+use crate::Coefficient;
+
+/// PID controller builder
+///
+/// Builds `Biquad` from action gains, gain limits, input offset and output limits.
+///
+/// ```
+/// # use idsp::iir::*;
+/// let b: Biquad<f32> = Pid::default()
+/// .period(1e-3)
+/// .gain(Action::Ki, 1e-3)
+/// .gain(Action::Kp, 1.0)
+/// .gain(Action::Kd, 1e2)
+/// .limit(Action::Ki, 1e3)
+/// .limit(Action::Kd, 1e1)
+/// .build()
+/// .unwrap()
+/// .into();
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
+pub struct Pid<T> {
+ period: T,
+ gains: [T; 5],
+ limits: [T; 5],
+}
+
+impl<T: Float> Default for Pid<T> {
+ fn default() -> Self {
+ Self {
+ period: T::one(),
+ gains: [T::zero(); 5],
+ limits: [T::infinity(); 5],
+ }
+ }
+}
+
+/// [`Pid::build()`] errors
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
+#[non_exhaustive]
+pub enum PidError {
+ /// The action gains cover more than three successive orders
+ OrderRange,
+}
+
+/// PID action
+///
+/// This enumerates the five possible PID style actions of a [`crate::iir::Biquad`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
+pub enum Action {
+ /// Double integrating, -40 dB per decade
+ Kii = 0,
+ /// Integrating, -20 dB per decade
+ Ki = 1,
+ /// Proportional
+ Kp = 2,
+ /// Derivative=, 20 dB per decade
+ Kd = 3,
+ /// Double derivative, 40 dB per decade
+ Kdd = 4,
+}
+
+impl<T: Float> Pid<T> {
+ /// Sample period
+ ///
+ /// # Arguments
+ /// * `period`: Sample period in some units, e.g. SI seconds
+ pub fn period(&mut self, period: T) -> &mut Self {
+ self.period = period;
+ self
+ }
+
+ /// Gain for a given action
+ ///
+ /// Gain units are `output/input * time.powi(order)` where
+ /// * `output` are output (`y`) units
+ /// * `input` are input (`x`) units
+ /// * `time` are sample period units, e.g. SI seconds
+ /// * `order` is the action order: the frequency exponent
+ /// (`-1` for integrating, `0` for proportional, etc.)
+ ///
+ /// Note that inverse time units correspond to angular frequency units.
+ /// Gains are accurate in the low frequency limit. Towards Nyquist, the
+ /// frequency response is warped.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let tau = 1e-3;
+ /// let ki = 1e-4;
+ /// let i: Biquad<f32> = Pid::default()
+ /// .period(tau)
+ /// .gain(Action::Ki, ki)
+ /// .build()
+ /// .unwrap()
+ /// .into();
+ /// let x0 = 5.0;
+ /// let y0 = i.update(&mut [0.0; 4], x0);
+ /// assert!((y0 / (x0 * ki / tau) - 1.0).abs() < 2.0 * f32::EPSILON);
+ /// ```
+ ///
+ /// # Arguments
+ /// * `action`: Action to control
+ /// * `gain`: Gain value
+ pub fn gain(&mut self, action: Action, gain: T) -> &mut Self {
+ self.gains[action as usize] = gain;
+ self
+ }
+
+ /// Gain limit for a given action
+ ///
+ /// Gain limit units are `output/input`. See also [`Pid::gain()`].
+ /// Multiple gains and limits may interact and lead to peaking.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let ki_limit = 1e3;
+ /// let i: Biquad<f32> = Pid::default()
+ /// .gain(Action::Ki, 8.0)
+ /// .limit(Action::Ki, ki_limit)
+ /// .build()
+ /// .unwrap()
+ /// .into();
+ /// let mut xy = [0.0; 4];
+ /// let x0 = 5.0;
+ /// for _ in 0..1000 {
+ /// i.update(&mut xy, x0);
+ /// }
+ /// let y0 = i.update(&mut xy, x0);
+ /// assert!((y0 / (x0 * ki_limit) - 1.0f32).abs() < 1e-3);
+ /// ```
+ ///
+ /// # Arguments
+ /// * `action`: Action to limit in gain
+ /// * `limit`: Gain limit
+ pub fn limit(&mut self, action: Action, limit: T) -> &mut Self {
+ self.limits[action as usize] = limit;
+ self
+ }
+
+ /// Perform checks, compute coefficients and return `Biquad`.
+ ///
+ /// No attempt is made to detect NaNs, non-finite gains, non-positive period,
+ /// zero gain limits, or gain/limit sign mismatches.
+ /// These will consequently result in NaNs/infinities, peaking, or notches in
+ /// the Biquad coefficients.
+ ///
+ /// Gain limits for zero gain actions or for proportional action are ignored.
+ ///
+ /// ```
+ /// # use idsp::iir::*;
+ /// let i: Biquad<f32> = Pid::default().gain(Action::Kp, 3.0).build().unwrap().into();
+ /// assert_eq!(i, Biquad::proportional(3.0));
+ /// ```
+ ///
+ /// # Panic
+ /// Will panic in debug mode on fixed point coefficient overflow.
+ pub fn build<C: Coefficient + AsPrimitive<T>>(&self) -> Result<[C; 5], PidError>
+ where
+ T: AsPrimitive<C>,
+ {
+ const KP: usize = Action::Kp as usize;
+
+ // Determine highest denominator (feedback, `a`) order
+ let low = self
+ .gains
+ .iter()
+ .take(KP)
+ .position(|g| !g.is_zero())
+ .unwrap_or(KP);
+
+ if self.gains.iter().skip(low + 3).any(|g| !g.is_zero()) {
+ return Err(PidError::OrderRange);
+ }
+
+ // Scale gains, compute limits
+ let mut zi = self.period.powi(low as i32 - KP as i32);
+ let mut gl = [[T::zero(); 2]; 3];
+ for (gli, (i, (ggi, lli))) in gl.iter_mut().zip(
+ self.gains
+ .iter()
+ .zip(self.limits.iter())
+ .enumerate()
+ .skip(low),
+ ) {
+ gli[0] = *ggi * zi;
+ gli[1] = if i == KP { T::one() } else { gli[0] / *lli };
+ zi = zi * self.period;
+ }
+ let a0i = T::one() / (gl[0][1] + gl[1][1] + gl[2][1]);
+
+ // Derivative/integration kernels
+ let kernels = [
+ [C::one(), C::zero(), C::zero()],
+ [C::one(), C::zero() - C::one(), C::zero()],
+ [C::one(), C::zero() - C::one() - C::one(), C::one()],
+ ];
+
+ // Coefficients
+ let mut ba = [[C::ZERO; 2]; 3];
+ for (gli, ki) in gl.iter().zip(kernels.iter()) {
+ // Quantize the gains and not the coefficients
+ let (g, l) = (C::quantize(gli[0] * a0i), C::quantize(gli[1] * a0i));
+ for (j, baj) in ba.iter_mut().enumerate() {
+ *baj = [baj[0] + ki[j] * g, baj[1] + ki[j] * l];
+ }
+ }
+
+ Ok([ba[0][0], ba[1][0], ba[2][0], ba[1][1], ba[2][1]])
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::iir::*;
+
+ #[test]
+ fn pid() {
+ let b: Biquad<f32> = Pid::default()
+ .period(1.0)
+ .gain(Action::Ki, 1e-3)
+ .gain(Action::Kp, 1.0)
+ .gain(Action::Kd, 1e2)
+ .limit(Action::Ki, 1e3)
+ .limit(Action::Kd, 1e1)
+ .build()
+ .unwrap()
+ .into();
+ let want = [
+ 9.18190826,
+ -18.27272561,
+ 9.09090826,
+ -1.90909074,
+ 0.90909083,
+ ];
+ for (ba_have, ba_want) in b.ba().iter().zip(want.iter()) {
+ assert!(
+ (ba_have / ba_want - 1.0).abs() < 2.0 * f32::EPSILON,
+ "have {:?} != want {want:?}",
+ b.ba(),
+ );
+ }
+ }
+
+ #[test]
+ fn pid_i32() {
+ let b: Biquad<i32> = Pid::default()
+ .period(1.0)
+ .gain(Action::Ki, 1e-5)
+ .gain(Action::Kp, 1e-2)
+ .gain(Action::Kd, 1e0)
+ .limit(Action::Ki, 1e1)
+ .limit(Action::Kd, 1e-1)
+ .build()
+ .unwrap()
+ .into();
+ println!("{b:?}");
+ }
+
+ #[test]
+ fn units() {
+ let ki = 5e-2;
+ let tau = 3e-3;
+ let b: Biquad<f32> = Pid::default()
+ .period(tau)
+ .gain(Action::Ki, ki)
+ .build()
+ .unwrap()
+ .into();
+ let mut xy = [0.0; 4];
+ for i in 1..10 {
+ let y_have = b.update(&mut xy, 1.0);
+ let y_want = (i as f32) * (ki / tau);
+ assert!(
+ (y_have / y_want - 1.0).abs() < 3.0 * f32::EPSILON,
+ "{i}: have {y_have} != {y_want}"
+ );
+ }
+ }
+}
+
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 +
#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
+#![doc = include_str!("../README.md")]
+#![deny(rust_2018_compatibility)]
+#![deny(rust_2018_idioms)]
+#![warn(missing_docs)]
+#![forbid(unsafe_code)]
+
+mod atan2;
+pub use atan2::*;
+mod accu;
+pub use accu::*;
+mod filter;
+pub use filter::*;
+mod complex;
+pub use complex::*;
+mod cossin;
+pub use cossin::*;
+pub mod iir;
+mod lockin;
+pub use lockin::*;
+mod lowpass;
+pub use lowpass::*;
+mod pll;
+pub use pll::*;
+mod rpll;
+pub use rpll::*;
+mod unwrap;
+pub use unwrap::*;
+pub mod hbf;
+mod num;
+pub use num::*;
+pub mod svf;
+
+#[cfg(test)]
+pub mod testing;
+
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 +
use super::{Complex, ComplexExt, Filter, MulScaled};
+
+/// Lockin filter
+///
+/// Combines two [`Filter`] and an NCO to perform demodulation
+#[derive(Copy, Clone, Default)]
+pub struct Lockin<T> {
+ state: [T; 2],
+}
+
+impl<T: Filter> Lockin<T> {
+ /// Update the lockin with a sample taken at a local oscillator IQ value.
+ pub fn update_iq(&mut self, sample: i32, lo: Complex<i32>, k: &T::Config) -> Complex<i32> {
+ let mix = lo.mul_scaled(sample);
+
+ // Filter with the IIR lowpass,
+ // return IQ (in-phase and quadrature) data.
+ Complex {
+ re: self.state[0].update(mix.re, k),
+ im: self.state[1].update(mix.im, k),
+ }
+ }
+
+ /// Update the lockin with a sample taken at a given phase.
+ pub fn update(&mut self, sample: i32, phase: i32, k: &T::Config) -> Complex<i32> {
+ // Get the LO signal for demodulation and mix the sample;
+ self.update_iq(sample, Complex::from_angle(phase), k)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +
use crate::Filter;
+
+/// Arbitrary order, high dynamic range, wide coefficient range,
+/// lowpass filter implementation. DC gain is 1.
+///
+/// Type argument N is the filter order. N must be `1` or `2`.
+///
+/// The filter will cleanly saturate towards the `i32` range.
+///
+/// Both filters have been optimized for accuracy, dynamic range, and
+/// speed on Cortex-M7.
+#[derive(Copy, Clone)]
+pub struct Lowpass<const N: usize>(pub(crate) [i64; N]);
+impl<const N: usize> Filter for Lowpass<N> {
+ /// The filter configuration `Config` contains the filter gains.
+ ///
+ /// For the first-order lowpass this is a single element array `[k]` with
+ /// the corner frequency in scaled Q31:
+ /// `k = pi*(1 << 31)*f0/fn` where
+ /// `f0` is the 3dB corner frequency and
+ /// `fn` is the Nyquist frequency.
+ /// The corner frequency is warped in the usual way.
+ ///
+ /// For the second-order lowpass this is `[k**2/(1 << 32), -k/q]` with `q = 1/sqrt(2)`
+ /// for a Butterworth response.
+ ///
+ /// In addition to the poles at the corner frequency the filters have zeros at Nyquist.
+ ///
+ /// The first-order lowpass works fine and accurate for any positive gain
+ /// `1 <= k <= (1 << 31) - 1`.
+ /// The second-order lowpass works and is accurate for
+ /// `1 << 16 <= k <= q*(1 << 31)`.
+ type Config = [i32; N];
+ fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
+ let mut d = x.saturating_sub(self.get()) as i64 * k[0] as i64;
+ let y;
+ if N == 1 {
+ self.0[0] += d;
+ y = self.get();
+ self.0[0] += d;
+ } else if N == 2 {
+ d += (self.0[1] >> 32) * k[1] as i64;
+ self.0[1] += d;
+ self.0[0] += self.0[1];
+ y = self.get();
+ // This creates the double Nyquist zero,
+ // compensates the gain lost in the signed i32 as (i32 as i64)*(i64 >> 32)
+ // multiplication while keeping the lowest bit significant, and
+ // copes better with wrap-around than Nyquist averaging.
+ self.0[0] += self.0[1];
+ self.0[1] += d;
+ } else {
+ unimplemented!()
+ }
+ y
+ }
+
+ fn get(&self) -> i32 {
+ (self.0[0] >> 32) as i32
+ }
+
+ fn set(&mut self, x: i32) {
+ self.0[0] = (x as i64) << 32;
+ }
+}
+
+impl<const N: usize> Default for Lowpass<N> {
+ fn default() -> Self {
+ Self([0; N])
+ }
+}
+
+/// First order lowpass
+pub type Lowpass1 = Lowpass<1>;
+/// Second order lowpass
+pub type Lowpass2 = Lowpass<2>;
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +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 +
use num_traits::{AsPrimitive, Float, Num};
+
+/// Helper trait unifying fixed point and floating point coefficients/samples
+pub trait Coefficient: 'static + Copy + Num + AsPrimitive<Self::ACCU> {
+ /// Multiplicative identity
+ const ONE: Self;
+ /// Negative multiplicative identity, equal to `-Self::ONE`.
+ const NEG_ONE: Self;
+ /// Additive identity
+ const ZERO: Self;
+ /// Lowest value
+ const MIN: Self;
+ /// Highest value
+ const MAX: Self;
+ /// Accumulator type
+ type ACCU: AsPrimitive<Self> + Num;
+
+ /// Proper scaling and potentially using a wide accumulator.
+ /// Clamp `self` such that `min <= self <= max`.
+ /// Undefined result if `max < min`.
+ fn macc(self, s: Self::ACCU, min: Self, max: Self, e1: Self) -> (Self, Self);
+
+ /// Clamp to between min and max
+ ///
+ /// Undefined if `min > max`.
+ fn clip(self, min: Self, max: Self) -> Self;
+
+ /// Multiplication (scaled)
+ fn mul_scaled(self, other: Self) -> Self;
+
+ /// Division (scaled)
+ fn div_scaled(self, other: Self) -> Self;
+
+ /// Scale and quantize a floating point value.
+ fn quantize<C>(value: C) -> Self
+ where
+ Self: AsPrimitive<C>,
+ C: Float + AsPrimitive<Self>;
+ // TODO: range check and Result
+}
+
+macro_rules! impl_float {
+ ($T:ty) => {
+ impl Coefficient for $T {
+ const ONE: Self = 1.0;
+ const NEG_ONE: Self = -1.0;
+ const ZERO: Self = 0.0;
+ const MIN: Self = <$T>::NEG_INFINITY;
+ const MAX: Self = <$T>::INFINITY;
+ type ACCU = Self;
+
+ #[inline]
+ fn macc(self, s: Self::ACCU, min: Self, max: Self, _e1: Self) -> (Self, Self) {
+ ((self + s).clip(min, max), 0.0)
+ }
+
+ #[inline]
+ fn clip(self, min: Self, max: Self) -> Self {
+ // <$T>::clamp() is slow and checks
+ self.max(min).min(max)
+ }
+
+ #[inline]
+ fn div_scaled(self, other: Self) -> Self {
+ self / other
+ }
+
+ #[inline]
+ fn mul_scaled(self, other: Self) -> Self {
+ self * other
+ }
+
+ #[inline]
+ fn quantize<C: Float + AsPrimitive<Self>>(value: C) -> Self {
+ value.as_()
+ }
+ }
+ };
+}
+impl_float!(f32);
+impl_float!(f64);
+
+macro_rules! impl_int {
+ ($T:ty, $U:ty, $A:ty, $Q:literal) => {
+ impl Coefficient for $T {
+ const ONE: Self = 1 << $Q;
+ const NEG_ONE: Self = -1 << $Q;
+ const ZERO: Self = 0;
+ const MIN: Self = <$T>::MIN;
+ const MAX: Self = <$T>::MAX;
+ type ACCU = $A;
+
+ #[inline]
+ fn macc(self, mut s: Self::ACCU, min: Self, max: Self, e1: Self) -> (Self, Self) {
+ const S: usize = core::mem::size_of::<$T>() * 8;
+ // Guard bits
+ const G: usize = S - $Q;
+ // Combine offset (u << $Q) with previous quantization error e1
+ s += (((self >> G) as $A) << S) | (((self << $Q) | e1) as $U as $A);
+ // Ord::clamp() is slow and checks
+ // This clamping truncates the lowest G bits of the value and the limits.
+ debug_assert_eq!(min & ((1 << G) - 1), 0);
+ debug_assert_eq!(max & ((1 << G) - 1), (1 << G) - 1);
+ let y0 = if (s >> S) as $T < (min >> G) {
+ min
+ } else if (s >> S) as $T > (max >> G) {
+ max
+ } else {
+ (s >> $Q) as $T
+ };
+ // Quantization error
+ let e0 = s as $T & ((1 << $Q) - 1);
+ (y0, e0)
+ }
+
+ #[inline]
+ fn clip(self, min: Self, max: Self) -> Self {
+ // Ord::clamp() is slow and checks
+ if self < min {
+ min
+ } else if self > max {
+ max
+ } else {
+ self
+ }
+ }
+
+ #[inline]
+ fn div_scaled(self, other: Self) -> Self {
+ (((self as $A) << $Q) / other as $A) as $T
+ }
+
+ #[inline]
+ fn mul_scaled(self, other: Self) -> Self {
+ (((1 << ($Q - 1)) + self as $A * other as $A) >> $Q) as $T
+ }
+
+ #[inline]
+ fn quantize<C>(value: C) -> Self
+ where
+ Self: AsPrimitive<C>,
+ C: Float + AsPrimitive<Self>,
+ {
+ (value * (1 << $Q).as_()).round().as_()
+ }
+ }
+ };
+}
+// Q2.X chosen to be able to exactly and inclusively represent -2 as `-1 << X + 1`
+// This is necessary to meet a1 = -2
+// It also create 2 guard bits for clamping in the accumulator which is often enough.
+impl_int!(i8, u8, i16, 6);
+impl_int!(i16, u16, i32, 14);
+impl_int!(i32, u32, i64, 30);
+impl_int!(i64, u64, i128, 62);
+
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 +
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.
+///
+/// This PLL implements first order noise shaping to reduce quantization errors.
+#[derive(Copy, Clone, Default, Deserialize, Serialize)]
+pub struct PLL {
+ // last input phase
+ x: i32,
+ // last output phase
+ y0: i32,
+ // last output frequency
+ f0: i32,
+ // filtered frequency
+ f: i64,
+ // filtered output phase
+ y: i64,
+}
+
+impl PLL {
+ /// Update the PLL with a new phase sample. This needs to be called (sampled) periodically.
+ /// The signal's phase/frequency is reconstructed relative to the sampling period.
+ ///
+ /// Args:
+ /// * `x`: New input phase sample or None if a sample has been missed.
+ /// * `k`: Feedback gain.
+ ///
+ /// Returns:
+ /// A tuple of instantaneous phase and frequency estimates.
+ pub fn update(&mut self, x: Option<i32>, k: i32) {
+ if let Some(x) = x {
+ let dx = x.wrapping_sub(self.x);
+ self.x = x;
+ let df = dx.wrapping_sub((self.f >> 32) as i32) as i64 * k as i64;
+ self.f = self.f.wrapping_add(df);
+ self.y = self.y.wrapping_add(self.f);
+ self.f = self.f.wrapping_add(df);
+ let dy = x.wrapping_sub((self.y >> 32) as i32) as i64 * k as i64;
+ self.y = self.y.wrapping_add(dy);
+ let y = (self.y >> 32) as i32;
+ self.y = self.y.wrapping_add(dy);
+ self.f0 = y.wrapping_sub(self.y0);
+ self.y0 = y;
+ } else {
+ self.y = self.y.wrapping_add(self.f);
+ self.x = self.x.wrapping_add(self.f0);
+ self.y0 = self.y0.wrapping_add(self.f0);
+ }
+ }
+
+ /// Return the current phase estimate
+ pub fn phase(&self) -> i32 {
+ self.y0
+ }
+
+ /// Return the current frequency estimate
+ pub fn frequency(&self) -> i32 {
+ self.f0
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn mini() {
+ let mut p = PLL::default();
+ let k = 1 << 24;
+ p.update(Some(0x10000), k);
+ assert_eq!(p.phase(), 0x1ff);
+ assert_eq!(p.frequency(), 0x1ff);
+ }
+
+ #[test]
+ fn converge() {
+ let mut p = PLL::default();
+ let k = 1 << 24;
+ let f0 = 0x71f63049_i32;
+ let n = 1 << 14;
+ let mut x = 0i32;
+ for i in 0..n {
+ x = x.wrapping_add(f0);
+ p.update(Some(x), k);
+ if i > n / 4 {
+ assert_eq!(p.frequency().wrapping_sub(f0).abs() <= 1, true);
+ }
+ if i > n / 2 {
+ assert_eq!(p.phase().wrapping_sub(x).abs() <= 1, true);
+ }
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +
/// 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 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);
+ // println!("{:?} {:?}", f, y);
+
+ let fm = f.iter().copied().sum::<f32>() / f.len() as f32;
+ let fs = f.iter().map(|f| (*f - fm).powi(2)).sum::<f32>().sqrt() / f.len() as f32;
+ let ym = y.iter().copied().sum::<f32>() / y.len() as f32;
+ let ys = y.iter().map(|y| (*y - ym).powi(2)).sum::<f32>().sqrt() / y.len() as f32;
+
+ 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 +54 +
//! State variable filter
+
+use num_traits::{Float, FloatConst};
+use serde::{Deserialize, Serialize};
+
+/// Second order state variable filter state
+pub struct State<T> {
+ /// Lowpass output
+ pub lp: T,
+ /// Highpass output
+ pub hp: T,
+ /// Bandpass output
+ pub bp: T,
+}
+
+impl<T: Float> State<T> {
+ /// Bandreject (notch) output
+ pub fn br(&self) -> T {
+ self.hp + self.lp
+ }
+}
+
+/// State variable filter
+///
+/// <https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/>
+#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
+pub struct Svf<T> {
+ f: T,
+ q: T,
+}
+
+impl<T: Float + FloatConst> Svf<T> {
+ /// Set the critical frequency
+ ///
+ /// In units of the sample frequency.
+ pub fn set_frequency(&mut self, f0: T) {
+ self.f = (T::one() + T::one()) * (T::PI() * f0).sin();
+ }
+
+ /// Set the Q parameter
+ pub fn set_q(&mut self, q: T) {
+ self.q = T::one() / q;
+ }
+
+ /// Update the filter
+ ///
+ /// Ingest an input sample and update state correspondingly.
+ /// Selected output(s) are available from [`State`].
+ pub fn update(&self, s: &mut State<T>, x0: T) {
+ s.lp = s.bp * self.f + s.lp;
+ s.hp = x0 - s.lp - s.bp * self.q;
+ s.bp = s.hp * self.f + s.bp;
+ }
+}
+
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 +
use core::{
+ cmp::PartialOrd,
+ ops::{BitAnd, Shr},
+};
+use num_traits::{
+ cast::AsPrimitive,
+ identities::Zero,
+ ops::wrapping::{WrappingAdd, WrappingSub},
+ Signed,
+};
+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<Q> {
+ /// current output
+ y: Q,
+}
+
+impl<Q> Unwrapper<Q>
+where
+ Q: 'static + WrappingAdd + Copy,
+{
+ /// Feed a new sample..
+ ///
+ /// Args:
+ /// * `x`: New sample
+ ///
+ /// Returns:
+ /// The (wrapped) difference `x - x_old`
+ pub fn update<P>(&mut self, x: P) -> P
+ where
+ P: 'static + WrappingSub + Copy + AsPrimitive<Q>,
+ Q: AsPrimitive<P>,
+ {
+ let dx = x.wrapping_sub(&self.y.as_());
+ self.y = self.y.wrapping_add(&dx.as_());
+ dx
+ }
+
+ /// The current number of wraps
+ pub fn wraps<P, const S: u32>(&self) -> P
+ where
+ Q: AsPrimitive<P> + Shr<u32, Output = Q>,
+ P: 'static + Copy + WrappingAdd + Signed + BitAnd<u32, Output = P>,
+ {
+ (self.y >> S)
+ .as_()
+ .wrapping_add(&((self.y >> (S - 1)).as_() & 1))
+ }
+
+ /// The current phase
+ pub fn phase<P>(&self) -> P
+ where
+ P: 'static + Copy,
+ Q: AsPrimitive<P>,
+ {
+ self.y.as_()
+ }
+
+ /// Current output including wraps
+ pub fn y(&self) -> Q {
+ self.y
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn overflowing_sub_correctness() {
+ for (x0, x1, v) in [
+ (0i32, 0i32, 0i32),
+ (0, 1, 0),
+ (0, -1, 0),
+ (1, 0, 0),
+ (-1, 0, 0),
+ (0, 0x7fff_ffff, 0),
+ (-1, 0x7fff_ffff, -1),
+ (-2, 0x7fff_ffff, -1),
+ (-1, -0x8000_0000, 0),
+ (0, -0x8000_0000, 0),
+ (1, -0x8000_0000, 1),
+ (-0x6000_0000, 0x6000_0000, -1),
+ (0x6000_0000, -0x6000_0000, 1),
+ (-0x4000_0000, 0x3fff_ffff, 0),
+ (-0x4000_0000, 0x4000_0000, -1),
+ (-0x4000_0000, 0x4000_0001, -1),
+ (0x4000_0000, -0x3fff_ffff, 0),
+ (0x4000_0000, -0x4000_0000, 0),
+ (0x4000_0000, -0x4000_0001, 1),
+ ]
+ .iter()
+ {
+ let (dx, w) = overflowing_sub(*x1, *x0);
+ assert_eq!(*v, w, " = overflowing_sub({:#x}, {:#x})", *x0, *x1);
+ let (dx0, w0) = x1.overflowing_sub(*x0);
+ assert_eq!(w0, w != 0);
+ assert_eq!(dx, dx0);
+ }
+ }
+
+ #[test]
+ fn saturating_scale_correctness() {
+ let shift = 8;
+ for (lo, hi, res) in [
+ (0i32, 0i32, 0i32),
+ (0, 1, 0x0100_0000),
+ (0, -1, -0x0100_0000),
+ (0x100, 0, 1),
+ (-1 << 31, 0, -1 << 23),
+ (0x7fffffff, 0, 0x007f_ffff),
+ (0x7fffffff, 1, 0x0017f_ffff),
+ (-0x7fffffff, -1, -0x0180_0000),
+ (0x1234_5600, 0x7f, 0x7f12_3456),
+ (0x1234_5600, -0x7f, -0x7f00_0000 + 0x12_3456),
+ (0, 0x7f, 0x7f00_0000),
+ (0, 0x80, 0x7fff_ff80),
+ (0, -0x7f, -0x7f00_0000),
+ (0, -0x80, -0x7fff_ff80),
+ (0x7fff_ffff, 0x7f, 0x7f7f_ffff),
+ (-0x8000_0000, 0x7f, 0x7e80_0000),
+ (-0x8000_0000, -0x7f, -0x7f80_0000),
+ (0x7fff_ffff, -0x7f, -0x7e80_0001),
+ (0x100, 0x7f, 0x7f00_0001),
+ (0, -0x80, -0x7fff_ff80),
+ (-1 << 31, 0x80, 0x7fff_ff80),
+ (-1 << 31, -0x80, -0x7fff_ff80),
+ ]
+ .iter()
+ {
+ let s = saturating_scale(*lo, *hi, shift);
+ assert_eq!(
+ *res, s,
+ "{:#x} != {:#x} = saturating_scale({:#x}, {:#x}, {:#x})",
+ *res, s, *lo, *hi, shift
+ );
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +
//! # 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, Filter, Lockin, Lowpass, Repeat, 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, SerialTerminal, SystemTimer, Systick,
+ UsbDevice, AFE0, AFE1,
+ },
+ net::{
+ data_stream::{FrameGenerator, StreamFormat, StreamTarget},
+ miniconf::Tree,
+ serde::{Deserialize, Serialize},
+ telemetry::{Telemetry, TelemetryBuffer},
+ NetworkState, NetworkUsers,
+ },
+};
+
+// The logarithm of the number of samples in each batch process. This corresponds with 2^3 samples
+// per batch = 8 samples
+const BATCH_SIZE_LOG2: u32 = 3;
+const BATCH_SIZE: usize = 1 << BATCH_SIZE_LOG2;
+
+// The logarithm of the number of 100MHz timer ticks between each sample. This corresponds with a
+// sampling period of 2^7 = 128 ticks. At 100MHz, 10ns per tick, this corresponds to a sampling
+// period of 1.28 uS or 781.25 KHz.
+const SAMPLE_TICKS_LOG2: u32 = 7;
+const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2;
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+enum Conf {
+ /// Output the lockin magnitude.
+ Magnitude,
+ /// Output the phase of the lockin
+ Phase,
+ /// Output the lockin reference frequency as a sinusoid
+ ReferenceFrequency,
+ /// Output the logarithmic power of the lockin
+ LogPower,
+ /// Output the in-phase component of the lockin signal.
+ InPhase,
+ /// Output the quadrature component of the lockin signal.
+ Quadrature,
+ /// Output the lockin internal modulation frequency as a sinusoid
+ Modulation,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
+enum LockinMode {
+ /// Utilize an internally generated reference for demodulation
+ Internal,
+ /// Utilize an external modulation signal supplied to DI0
+ External,
+}
+
+#[derive(Copy, Clone, Debug, Tree)]
+pub struct Settings {
+ /// Configure the Analog Front End (AFE) gain.
+ ///
+ /// # Path
+ /// `afe/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// Any of the variants of [Gain] enclosed in double quotes.
+ #[tree]
+ afe: [Gain; 2],
+
+ /// Specifies the operational mode of the lockin.
+ ///
+ /// # Path
+ /// `lockin_mode`
+ ///
+ /// # Value
+ /// One of the variants of [LockinMode] enclosed in double quotes.
+ lockin_mode: LockinMode,
+
+ /// Specifis the PLL time constant.
+ ///
+ /// # Path
+ /// `pll_tc/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// The PLL time constant exponent (1-31).
+ pll_tc: [u32; 2],
+
+ /// Specifies the lockin lowpass gains.
+ ///
+ /// # Path
+ /// `lockin_k`
+ ///
+ /// # Value
+ /// The lockin low-pass coefficients. See [`idsp::Lowpass`] for determining them.
+ lockin_k: <Lowpass<2> as Filter>::Config,
+
+ /// Specifies which harmonic to use for the lockin.
+ ///
+ /// # Path
+ /// `lockin_harmonic`
+ ///
+ /// # Value
+ /// Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate)
+ lockin_harmonic: i32,
+
+ /// Specifies the LO phase offset.
+ ///
+ /// # Path
+ /// `lockin_phase`
+ ///
+ /// # Value
+ /// Demodulation LO phase offset. Units are in terms of i32, where [i32::MIN] is equivalent to
+ /// -pi and [i32::MAX] is equivalent to +pi.
+ lockin_phase: i32,
+
+ /// Specifies DAC output mode.
+ ///
+ /// # Path
+ /// `output_conf/<n>`
+ ///
+ /// * `<n>` specifies which channel to configure. `<n>` := [0, 1]
+ ///
+ /// # Value
+ /// One of the variants of [Conf] enclosed in double quotes.
+ #[tree]
+ output_conf: [Conf; 2],
+
+ /// Specifies the telemetry output period in seconds.
+ ///
+ /// # Path
+ /// `telemetry_period`
+ ///
+ /// # Value
+ /// Any non-zero value less than 65536.
+ telemetry_period: u16,
+
+ /// Specifies the target for data livestreaming.
+ ///
+ /// # Path
+ /// `stream_target`
+ ///
+ /// # Value
+ /// See [StreamTarget#miniconf]
+ stream_target: StreamTarget,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ afe: [Gain::G1; 2],
+
+ lockin_mode: LockinMode::External,
+
+ pll_tc: [21, 21], // frequency and phase settling time (log2 counter cycles)
+
+ lockin_k: [0x8_0000, -0x400_0000], // lockin lowpass gains
+ lockin_harmonic: -1, // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
+ lockin_phase: 0, // Demodulation LO phase offset
+
+ output_conf: [Conf::InPhase, Conf::Quadrature],
+ // The default telemetry period in seconds.
+ telemetry_period: 10,
+
+ stream_target: StreamTarget::default(),
+ }
+ }
+}
+
+#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, SDMMC])]
+mod app {
+ use super::*;
+
+ #[monotonic(binds = SysTick, default = true, priority = 2)]
+ type Monotonic = Systick;
+
+ #[shared]
+ struct Shared {
+ usb: UsbDevice,
+ network: NetworkUsers<Settings, Telemetry, 2>,
+ settings: Settings,
+ telemetry: TelemetryBuffer,
+ }
+
+ #[local]
+ struct Local {
+ usb_terminal: SerialTerminal,
+ sampling_timer: SamplingTimer,
+ digital_inputs: (DigitalInput0, DigitalInput1),
+ timestamper: InputStamper,
+ afes: (AFE0, AFE1),
+ adcs: (Adc0Input, Adc1Input),
+ dacs: (Dac0Output, Dac1Output),
+ pll: RPLL,
+ lockin: Lockin<Repeat<2, Lowpass<2>>>,
+ signal_generator: signal_generator::SignalGenerator,
+ generator: FrameGenerator,
+ cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor,
+ }
+
+ #[init]
+ fn init(c: init::Context) -> (Shared, Local, init::Monotonics) {
+ let clock = SystemTimer::new(|| monotonics::now().ticks() as u32);
+
+ // Configure the microcontroller
+ let (mut stabilizer, _pounder) = hardware::setup::setup(
+ c.core,
+ c.device,
+ clock,
+ BATCH_SIZE,
+ SAMPLE_TICKS,
+ );
+
+ let settings = stabilizer.usb_serial.settings();
+ let mut network = NetworkUsers::new(
+ stabilizer.net.stack,
+ stabilizer.net.phy,
+ clock,
+ env!("CARGO_BIN_NAME"),
+ &settings.broker,
+ &settings.id,
+ stabilizer.metadata,
+ );
+
+ let generator = network.configure_streaming(StreamFormat::AdcDacData);
+
+ let shared = Shared {
+ network,
+ usb: stabilizer.usb,
+ 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 {
+ usb_terminal: stabilizer.usb_serial,
+ sampling_timer: stabilizer.adc_dac_timer,
+ digital_inputs: stabilizer.digital_inputs,
+ afes: stabilizer.afes,
+ adcs: stabilizer.adcs,
+ dacs: stabilizer.dacs,
+ timestamper: stabilizer.timestamper,
+
+ pll: RPLL::new(SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2),
+ lockin: Lockin::default(),
+ signal_generator: signal_generator::SignalGenerator::new(
+ signal_config,
+ ),
+
+ generator,
+ cpu_temp_sensor: stabilizer.temperature_sensor,
+ };
+
+ // Enable ADC/DAC events
+ local.adcs.0.start();
+ local.adcs.1.start();
+ local.dacs.0.start();
+ local.dacs.1.start();
+
+ // Spawn a settings and telemetry update for default settings.
+ settings_update::spawn().unwrap();
+ telemetry::spawn().unwrap();
+ ethernet_link::spawn().unwrap();
+ start::spawn_after(100.millis()).unwrap();
+
+ // Start recording digital input timestamps.
+ stabilizer.timestamp_timer.start();
+
+ // Enable the timestamper.
+ local.timestamper.start();
+
+ (shared, local, init::Monotonics(stabilizer.systick))
+ }
+
+ #[task(priority = 1, local=[sampling_timer])]
+ fn start(c: start::Context) {
+ // Start sampling ADCs and DACs.
+ c.local.sampling_timer.start();
+ }
+
+ /// Main DSP processing routine.
+ ///
+ /// See `dual-iir` for general notes on processing time and timing.
+ ///
+ /// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal.
+ /// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale.
+ /// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available.
+ #[task(binds=DMA1_STR4, shared=[settings, telemetry], local=[adcs, dacs, lockin, timestamper, pll, generator, signal_generator], priority=3)]
+ #[link_section = ".itcm.process"]
+ fn process(c: process::Context) {
+ let process::SharedResources {
+ settings,
+ telemetry,
+ } = c.shared;
+
+ let process::LocalResources {
+ timestamper,
+ adcs: (adc0, adc1),
+ dacs: (dac0, dac1),
+ pll,
+ lockin,
+ signal_generator,
+ generator,
+ } = c.local;
+
+ (settings, telemetry).lock(|settings, telemetry| {
+ let (reference_phase, reference_frequency) =
+ match settings.lockin_mode {
+ LockinMode::External => {
+ let timestamp =
+ timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
+ let (pll_phase, pll_frequency) = pll.update(
+ timestamp.map(|t| t as i32),
+ settings.pll_tc[0],
+ settings.pll_tc[1],
+ );
+ (pll_phase, (pll_frequency >> BATCH_SIZE_LOG2) as i32)
+ }
+ LockinMode::Internal => {
+ // Reference phase and frequency are known.
+ (1i32 << 30, 1i32 << (32 - BATCH_SIZE_LOG2))
+ }
+ };
+
+ let sample_frequency =
+ reference_frequency.wrapping_mul(settings.lockin_harmonic);
+ let sample_phase = settings.lockin_phase.wrapping_add(
+ reference_phase.wrapping_mul(settings.lockin_harmonic),
+ );
+
+ (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
+ let adc_samples = [adc0, adc1];
+ let mut dac_samples = [dac0, dac1];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+
+ let output: Complex<i32> = adc_samples[0]
+ .iter()
+ // Zip in the LO phase.
+ .zip(Accu::new(sample_phase, sample_frequency))
+ // Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter)
+ .map(|(&sample, phase)| {
+ let s = (sample as i16 as i32) << 16;
+ lockin.update(s, phase, &settings.lockin_k)
+ })
+ // Decimate
+ .last()
+ .unwrap()
+ * 2; // Full scale assuming the 2f component is gone.
+
+ // Convert to DAC data.
+ for (channel, samples) in dac_samples.iter_mut().enumerate() {
+ for sample in samples.iter_mut() {
+ let value = match settings.output_conf[channel] {
+ Conf::Magnitude => output.abs_sqr() as i32 >> 16,
+ Conf::Phase => output.arg() >> 16,
+ Conf::LogPower => output.log2() << 8,
+ Conf::ReferenceFrequency => {
+ reference_frequency >> 16
+ }
+ Conf::InPhase => output.re >> 16,
+ Conf::Quadrature => output.im >> 16,
+
+ Conf::Modulation => {
+ signal_generator.next().unwrap() as i32
+ }
+ };
+
+ *sample = DacCode::from(value as i16).0;
+ }
+ }
+
+ // Stream the data.
+ const N: usize = BATCH_SIZE * core::mem::size_of::<i16>()
+ / core::mem::size_of::<MaybeUninit<u8>>();
+ generator.add(|buf| {
+ for (data, buf) in adc_samples
+ .iter()
+ .chain(dac_samples.iter())
+ .zip(buf.chunks_exact_mut(N))
+ {
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ data.as_ptr() as *const MaybeUninit<u8>,
+ N,
+ )
+ };
+ buf.copy_from_slice(data)
+ }
+ N * 4
+ });
+
+ // Update telemetry measurements.
+ telemetry.adcs =
+ [AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
+
+ telemetry.dacs =
+ [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
+
+ // Preserve instruction and data ordering w.r.t. DMA flag access.
+ fence(Ordering::SeqCst);
+ });
+ });
+ }
+
+ #[idle(shared=[network, usb])]
+ fn idle(mut c: idle::Context) -> ! {
+ loop {
+ match c.shared.network.lock(|net| net.update()) {
+ NetworkState::SettingsChanged(_path) => {
+ settings_update::spawn().unwrap()
+ }
+ NetworkState::Updated => {}
+ NetworkState::NoChange => {
+ // We can't sleep if USB is not in suspend.
+ if c.shared.usb.lock(|usb| {
+ usb.state()
+ == usb_device::device::UsbDeviceState::Suspend
+ }) {
+ cortex_m::asm::wfi();
+ }
+ }
+ }
+ }
+ }
+
+ #[task(priority = 1, local=[afes], shared=[network, settings])]
+ fn settings_update(mut c: settings_update::Context) {
+ let settings = c.shared.network.lock(|net| *net.miniconf.settings());
+ c.shared.settings.lock(|current| *current = settings);
+
+ c.local.afes.0.set_gain(settings.afe[0]);
+ c.local.afes.1.set_gain(settings.afe[1]);
+
+ let target = settings.stream_target.into();
+ c.shared.network.lock(|net| net.direct_stream(target));
+ }
+
+ #[task(priority = 1, local=[digital_inputs, cpu_temp_sensor], shared=[network, settings, telemetry])]
+ fn telemetry(mut c: telemetry::Context) {
+ let mut telemetry: TelemetryBuffer =
+ c.shared.telemetry.lock(|telemetry| *telemetry);
+
+ telemetry.digital_inputs = [
+ c.local.digital_inputs.0.is_high(),
+ c.local.digital_inputs.1.is_high(),
+ ];
+
+ let (gains, telemetry_period) = c
+ .shared
+ .settings
+ .lock(|settings| (settings.afe, settings.telemetry_period));
+
+ c.shared.network.lock(|net| {
+ net.telemetry.publish(&telemetry.finalize(
+ gains[0],
+ gains[1],
+ c.local.cpu_temp_sensor.get_temperature().unwrap(),
+ ))
+ });
+
+ // Schedule the telemetry task in the future.
+ telemetry::Monotonic::spawn_after((telemetry_period as u64).secs())
+ .unwrap();
+ }
+
+ #[task(priority = 1, shared=[usb], local=[usb_terminal])]
+ fn usb(mut c: usb::Context) {
+ // Handle the USB serial terminal.
+ c.shared.usb.lock(|usb| {
+ usb.poll(&mut [c.local.usb_terminal.interface_mut().inner_mut()]);
+ });
+
+ c.local.usb_terminal.process().unwrap();
+
+ // Schedule to run this task every 10 milliseconds.
+ usb::spawn_after(10u64.millis()).unwrap();
+ }
+
+ #[task(priority = 1, shared=[network])]
+ fn ethernet_link(mut c: ethernet_link::Context) {
+ c.shared.network.lock(|net| net.processor.handle_link());
+ ethernet_link::Monotonic::spawn_after(1.secs()).unwrap();
+ }
+
+ #[task(binds = ETH, priority = 1)]
+ fn eth(_: eth::Context) {
+ unsafe { hal::ethernet::interrupt_handler() }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +
use crate::{Error, Increment, Key, Metadata, TreeDeserialize, TreeKey, TreeSerialize};
+use serde::{de::Deserialize, Deserializer, Serialize, Serializer};
+
+/// Returns the number of digits required to format an integer less than `x`.
+const fn digits(x: usize) -> usize {
+ let mut max = 10;
+ let mut digits = 1;
+
+ while x > max {
+ max *= 10;
+ digits += 1;
+ }
+ digits
+}
+
+// Y >= 2
+macro_rules! depth {
+ ($($y:literal)+) => {$(
+ impl<T: TreeKey<{$y - 1}>, const N: usize> TreeKey<$y> for [T; N] {
+ fn name_to_index(value: &str) -> Option<usize> {
+ value.parse().ok()
+ }
+ fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ if index >= N {
+ return Err(Error::NotFound(1));
+ }
+ func(index, itoa::Buffer::new().format(index))?;
+ T::traverse_by_key(keys, func).increment()
+ }
+
+ fn metadata() -> Metadata {
+ let mut meta = T::metadata();
+
+ meta.max_length += digits(N);
+ meta.max_depth += 1;
+ meta.count *= N;
+
+ meta
+ }
+ }
+
+ impl<T: TreeSerialize<{$y - 1}>, const N: usize> TreeSerialize<$y> for [T; N] {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get(index).ok_or(Error::NotFound(1))?;
+ item.serialize_by_key(keys, ser).increment()
+ }
+ }
+
+ impl<'de, T: TreeDeserialize<'de, {$y - 1}>, const N: usize> TreeDeserialize<'de, $y> for [T; N] {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<$y, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get_mut(index).ok_or(Error::NotFound(1))?;
+ item.deserialize_by_key(keys, de).increment()
+ }
+ }
+ )+}
+}
+depth!(2 3 4 5 6 7 8);
+
+// Y == 1
+impl<T, const N: usize> TreeKey for [T; N] {
+ fn name_to_index(value: &str) -> Option<usize> {
+ value.parse().ok()
+ }
+
+ fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ match key.find::<1, Self>() {
+ Some(index) if index < N => {
+ func(index, itoa::Buffer::new().format(index))?;
+ Ok(1)
+ }
+ _ => Err(Error::NotFound(1)),
+ }
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ max_length: digits(N),
+ max_depth: 1,
+ count: N,
+ }
+ }
+}
+
+impl<T: Serialize, const N: usize> TreeSerialize for [T; N] {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<1, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get(index).ok_or(Error::NotFound(1))?;
+ // Precedence
+ if keys.next().is_some() {
+ Err(Error::TooLong(1))
+ } else {
+ item.serialize(ser)?;
+ Ok(1)
+ }
+ }
+}
+
+impl<'de, T: Deserialize<'de>, const N: usize> TreeDeserialize<'de> for [T; N] {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ let key = keys.next().ok_or(Error::TooShort(0))?;
+ let index = key.find::<1, Self>().ok_or(Error::NotFound(1))?;
+ let item = self.get_mut(index).ok_or(Error::NotFound(1))?;
+ // Precedence
+ if keys.next().is_some() {
+ Err(Error::TooLong(1))
+ } else {
+ *item = T::deserialize(de)?;
+ Ok(1)
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +
use crate::{Error, TreeKey};
+use core::{fmt::Write, marker::PhantomData};
+
+/// An iterator over the paths in a `TreeKey`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PathIter<'a, M: ?Sized, const Y: usize, P> {
+ /// Zero-size markers to allow being generic over M/P (by constraining the type parameters).
+ m: PhantomData<M>,
+ p: PhantomData<P>,
+
+ /// The iteration state.
+ ///
+ /// It contains the current field/element index at each path hierarchy level
+ /// and needs to be at least as large as the maximum path depth.
+ state: [usize; Y],
+
+ /// The remaining length of the iterator.
+ ///
+ /// It is used to provide an exact and trusted [Iterator::size_hint] ([core::iter::TrustedLen]).
+ ///
+ /// It may be None to indicate unknown length.
+ count: Option<usize>,
+
+ /// The separator before each name.
+ separator: &'a str,
+}
+
+impl<'a, M, const Y: usize, P> PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y> + ?Sized,
+{
+ pub(crate) fn new(separator: &'a str) -> Self {
+ let meta = M::metadata();
+ assert!(Y >= meta.max_depth);
+ let mut s = Self::new_unchecked(separator);
+ s.count = Some(meta.count);
+ s
+ }
+
+ pub(crate) fn new_unchecked(separator: &'a str) -> Self {
+ Self {
+ count: None,
+ separator,
+ state: [0; Y],
+ m: PhantomData,
+ p: PhantomData,
+ }
+ }
+}
+
+impl<'a, M, const Y: usize, P> Iterator for PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y> + ?Sized,
+ P: Write + Default,
+{
+ type Item = Result<P, core::fmt::Error>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut path = P::default();
+
+ loop {
+ return match M::path(self.state, &mut path, self.separator) {
+ // Out of valid indices at the root: iteration done
+ Err(Error::NotFound(1)) => {
+ debug_assert_eq!(self.count.unwrap_or_default(), 0);
+ None
+ }
+ // Node not found at depth: reset current index, increment parent index,
+ // then retry path()
+ Err(Error::NotFound(depth @ 2..)) => {
+ path = P::default();
+ self.state[depth - 1] = 0;
+ self.state[depth - 2] += 1;
+ continue;
+ }
+ // Found a leaf at the root: leaf Option/newtype
+ // Since there is no way to end iteration by hoping for `NotFound` on a leaf Option,
+ // we force the count to Some(0) and trigger on that.
+ Ok(0) => {
+ if self.count == Some(0) {
+ None
+ } else {
+ debug_assert_eq!(self.count.unwrap_or(1), 1);
+ self.count = Some(0);
+ Some(Ok(path))
+ }
+ }
+ // Non-root leaf: advance index at current depth
+ Ok(depth) => {
+ self.count = self.count.map(|c| c - 1);
+ self.state[depth - 1] += 1;
+ Some(Ok(path))
+ }
+ // `core::fmt::Write` error (e.g. heapless::String capacity limit).
+ Err(Error::Inner(e @ core::fmt::Error)) => Some(Err(e)),
+ // * NotFound(0) Not having consumed any name/index, the only possible case
+ // is a leaf (e.g. `Option` or newtype), those however can not return `NotFound`.
+ // * TooShort is excluded by construction.
+ // * No other errors are returned by traverse_by_key()/path()
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.count.unwrap_or_default(), self.count)
+ }
+}
+
+impl<'a, M, const Y: usize, P> core::iter::FusedIterator for PathIter<'a, M, Y, P>
+where
+ M: TreeKey<Y>,
+ P: Write + Default,
+{
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +
use crate::{Error, TreeDeserialize, TreeSerialize};
+use serde_json_core::{de, ser};
+
+/// Miniconf with "JSON and `/`".
+///
+/// Access items with `'/'` as path separator and JSON (from `serde-json-core`)
+/// as serialization/deserialization payload format.
+pub trait JsonCoreSlash<'de, const Y: usize = 1>:
+ TreeSerialize<Y> + TreeDeserialize<'de, Y>
+{
+ /// Update an element by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element. Everything before the first `'/'` is ignored.
+ /// * `data` - The serialized data making up the content.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `data` or an [Error].
+ fn set_json(&mut self, path: &str, data: &'de [u8]) -> Result<usize, Error<de::Error>>;
+
+ /// Retrieve a serialized value by path.
+ ///
+ /// # Args
+ /// * `path` - The path to the element.
+ /// * `data` - The buffer to serialize the data into.
+ ///
+ /// # Returns
+ /// The number of bytes used in the `data` buffer or an [Error].
+ fn get_json(&self, path: &str, data: &mut [u8]) -> Result<usize, Error<ser::Error>>;
+
+ /// Update an element by indices.
+ ///
+ /// # Args
+ /// * `indices` - The indices to the element. Everything before the first `'/'` is ignored.
+ /// * `data` - The serialized data making up the content.
+ ///
+ /// # Returns
+ /// The number of bytes consumed from `data` or an [Error].
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8],
+ ) -> Result<usize, Error<de::Error>>;
+
+ /// Retrieve a serialized value by indices.
+ ///
+ /// # Args
+ /// * `indices` - The indices to the element.
+ /// * `data` - The buffer to serialize the data into.
+ ///
+ /// # Returns
+ /// The number of bytes used in the `data` buffer or an [Error].
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8],
+ ) -> Result<usize, Error<ser::Error>>;
+}
+
+impl<'de, T: TreeSerialize<Y> + TreeDeserialize<'de, Y>, const Y: usize> JsonCoreSlash<'de, Y>
+ for T
+{
+ fn set_json(&mut self, path: &str, data: &'de [u8]) -> Result<usize, Error<de::Error>> {
+ let mut de = de::Deserializer::new(data);
+ self.deserialize_by_key(path.split('/').skip(1), &mut de)?;
+ de.end().map_err(Error::PostDeserialization)
+ }
+
+ fn get_json(&self, path: &str, data: &mut [u8]) -> Result<usize, Error<ser::Error>> {
+ let mut ser = ser::Serializer::new(data);
+ self.serialize_by_key(path.split('/').skip(1), &mut ser)?;
+ Ok(ser.end())
+ }
+
+ fn set_json_by_index(
+ &mut self,
+ indices: &[usize],
+ data: &'de [u8],
+ ) -> Result<usize, Error<de::Error>> {
+ let mut de = de::Deserializer::new(data);
+ self.deserialize_by_key(indices.iter().copied(), &mut de)?;
+ de.end().map_err(Error::PostDeserialization)
+ }
+
+ fn get_json_by_index(
+ &self,
+ indices: &[usize],
+ data: &mut [u8],
+ ) -> Result<usize, Error<ser::Error>> {
+ let mut ser = ser::Serializer::new(data);
+ self.serialize_by_key(indices.iter().copied(), &mut ser)?;
+ Ok(ser.end())
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +
#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
+#![cfg_attr(feature = "json-core", doc = include_str!("../README.md"))]
+#![cfg_attr(not(feature = "json-core"), doc = "Miniconf")]
+#![deny(rust_2018_compatibility)]
+#![deny(rust_2018_idioms)]
+#![warn(missing_docs)]
+#![forbid(unsafe_code)]
+
+pub use miniconf_derive::*;
+mod tree;
+pub use tree::*;
+mod array;
+pub use array::*;
+mod iter;
+pub use iter::*;
+mod option;
+pub use option::*;
+
+#[cfg(feature = "json-core")]
+mod json_core;
+#[cfg(feature = "json-core")]
+pub use json_core::*;
+
+#[cfg(feature = "mqtt-client")]
+mod mqtt_client;
+#[cfg(feature = "mqtt-client")]
+pub use mqtt_client::*;
+
+#[cfg(feature = "mqtt-client")]
+pub use minimq;
+
+// re-export for proc-macro
+#[doc(hidden)]
+pub use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +
use crate::{Error, JsonCoreSlash, TreeKey};
+use heapless::{String, Vec};
+use minimq::{
+ embedded_nal::TcpClientStack,
+ embedded_time,
+ types::{SubscriptionOptions, TopicFilter},
+ DeferredPublication, Publication, QoS,
+};
+
+use embedded_io::Write;
+
+// The maximum topic length of any settings path.
+const MAX_TOPIC_LENGTH: usize = 128;
+
+// The maximum amount of correlation data that will be cached for listing. This is set to function
+// with the Miniconf python client (i.e. 32 bytes can encode a UUID).
+const MAX_CD_LENGTH: usize = 32;
+
+// The delay after not receiving messages after initial connection that settings will be
+// republished.
+const REPUBLISH_TIMEOUT_SECONDS: u32 = 2;
+
+type Iter<M, const Y: usize> = crate::PathIter<'static, M, Y, String<MAX_TOPIC_LENGTH>>;
+
+mod sm {
+ use super::{Iter, TreeKey, REPUBLISH_TIMEOUT_SECONDS};
+ use minimq::embedded_time::{self, duration::Extensions, Instant};
+ use smlang::statemachine;
+
+ statemachine! {
+ transitions: {
+ *Initial + Connected = ConnectedToBroker,
+ ConnectedToBroker + IndicatedLife = PendingSubscribe,
+
+ // After initial subscriptions, we start a timeout to republish all settings.
+ PendingSubscribe + Subscribed / start_republish_timeout = PendingRepublish,
+
+ // Settings republish can be completed any time after subscription.
+ PendingRepublish + StartRepublish / start_republish = RepublishingSettings,
+ RepublishingSettings + StartRepublish / start_republish = RepublishingSettings,
+ Active + StartRepublish / start_republish = RepublishingSettings,
+
+ // After republishing settings, we are in an idle "active" state.
+ RepublishingSettings + Complete = Active,
+
+ // All states transition back to `initial` on reset.
+ _ + Reset = Initial,
+ }
+ }
+
+ pub struct Context<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> {
+ clock: C,
+ timeout: Option<Instant<C>>,
+ pub republish_state: Iter<M, Y>,
+ }
+
+ impl<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> Context<C, M, Y> {
+ pub fn new(clock: C) -> Self {
+ Self {
+ clock,
+ timeout: None,
+ // Skip redundant check (done comprehensively in `MqttClient::new()`)
+ republish_state: M::iter_paths_unchecked("/"),
+ }
+ }
+
+ pub fn republish_has_timed_out(&self) -> bool {
+ if let Some(timeout) = self.timeout {
+ self.clock.try_now().unwrap() > timeout
+ } else {
+ false
+ }
+ }
+ }
+
+ impl<C: embedded_time::Clock, M: TreeKey<Y>, const Y: usize> StateMachineContext
+ for Context<C, M, Y>
+ {
+ fn start_republish_timeout(&mut self) {
+ self.timeout
+ .replace(self.clock.try_now().unwrap() + REPUBLISH_TIMEOUT_SECONDS.seconds());
+ }
+
+ fn start_republish(&mut self) {
+ // Skip redundant check (done comprehensively in `MqttClient::new()`)
+ self.republish_state = M::iter_paths_unchecked("/");
+ }
+ }
+}
+
+enum Command<'a> {
+ List,
+ Get { path: &'a str },
+ Set { path: &'a str, value: &'a [u8] },
+}
+
+impl<'a> Command<'a> {
+ fn from_message(topic: &'a str, value: &'a [u8]) -> Result<Self, ()> {
+ let path = topic.strip_prefix('/').unwrap_or(topic);
+
+ if path == "list" {
+ Ok(Command::List)
+ } else {
+ match path.strip_prefix("settings") {
+ Some(path) => {
+ if value.is_empty() {
+ Ok(Command::Get { path })
+ } else {
+ Ok(Command::Set { path, value })
+ }
+ }
+ _ => Err(()),
+ }
+ }
+ }
+}
+
+struct ListCache {
+ topic: String<MAX_TOPIC_LENGTH>,
+ correlation_data: Option<Vec<u8, MAX_CD_LENGTH>>,
+}
+
+/// MQTT settings interface.
+///
+/// # Design
+/// The MQTT client places the [TreeKey] paths `<path>` at the MQTT `<prefix>/settings/<path>` topic,
+/// where `<prefix>` is provided in the client constructor.
+///
+/// It publishes its alive-ness as a `1` to `<prefix>/alive` and sets a will to publish `0` there when
+/// it is disconnected.
+///
+/// # Limitations
+/// The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to
+/// connect to it when errors occur.
+///
+/// The client only supports paths up to `MAX_TOPIC_LENGTH = 128` byte length.
+/// Re-publication timeout is fixed to `REPUBLISH_TIMEOUT_SECONDS = 2` seconds.
+///
+/// # Example
+/// ```
+/// use miniconf::{MqttClient, Tree};
+///
+/// #[derive(Tree, Clone, Default)]
+/// struct Settings {
+/// foo: bool,
+/// }
+///
+/// let mut buffer = [0u8; 1024];
+/// let localhost: minimq::embedded_nal::IpAddr = "127.0.0.1".parse().unwrap();
+/// let mut client: MqttClient<'_, _, _, _, minimq::broker::IpBroker, 1> = MqttClient::new(
+/// std_embedded_nal::Stack::default(),
+/// "quartiq/application/12345", // prefix
+/// std_embedded_time::StandardClock::default(),
+/// Settings::default(),
+/// minimq::ConfigBuilder::new(localhost.into(), &mut buffer).keepalive_interval(60),
+/// )
+/// .unwrap();
+///
+/// client
+/// .handled_update(|path, old_settings, new_settings| {
+/// if new_settings.foo {
+/// return Err("Foo!");
+/// }
+/// *old_settings = new_settings.clone();
+/// Ok(())
+/// })
+/// .unwrap();
+/// ```
+pub struct MqttClient<'buf, Settings, Stack, Clock, Broker, const Y: usize>
+where
+ Settings: TreeKey<Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock,
+ Broker: minimq::Broker,
+{
+ mqtt: minimq::Minimq<'buf, Stack, Clock, Broker>,
+ settings: Settings,
+ state: sm::StateMachine<sm::Context<Clock, Settings, Y>>,
+ prefix: String<MAX_TOPIC_LENGTH>,
+ listing_state: Option<(ListCache, Iter<Settings, Y>)>,
+}
+
+impl<'buf, Settings, Stack, Clock, Broker, const Y: usize>
+ MqttClient<'buf, Settings, Stack, Clock, Broker, Y>
+where
+ for<'de> Settings: JsonCoreSlash<'de, Y> + Clone,
+ Stack: TcpClientStack,
+ Clock: embedded_time::Clock + Clone,
+ Broker: minimq::Broker,
+{
+ /// Construct a new MQTT settings interface.
+ ///
+ /// # Args
+ /// * `stack` - The network stack to use for communication.
+ /// * `prefix` - The MQTT device prefix to use for this device.
+ /// * `clock` - The clock for managing the MQTT connection.
+ /// * `settings` - The initial settings values.
+ /// * `config` - The configuration of the MQTT client.
+ pub fn new(
+ stack: Stack,
+ prefix: &str,
+ clock: Clock,
+ settings: Settings,
+ config: minimq::ConfigBuilder<'buf, Broker>,
+ ) -> Result<Self, minimq::ProtocolError> {
+ // Configure a will so that we can indicate whether or not we are connected.
+ let prefix = String::from(prefix);
+ let mut connection_topic = prefix.clone();
+ connection_topic.push_str("/alive").unwrap();
+ let will = minimq::Will::new(&connection_topic, b"0", &[])?
+ .retained()
+ .qos(QoS::AtMostOnce);
+
+ let config = config.autodowngrade_qos().will(will)?;
+
+ let mqtt = minimq::Minimq::new(stack, clock.clone(), config);
+
+ let meta = Settings::metadata().separator("/");
+ assert!(prefix.len() + "/settings".len() + meta.max_length <= MAX_TOPIC_LENGTH);
+
+ Ok(Self {
+ mqtt,
+ state: sm::StateMachine::new(sm::Context::new(clock)),
+ settings,
+ prefix,
+ listing_state: None,
+ })
+ }
+
+ fn handle_listing(&mut self) {
+ let Some((cache, iter)) = &mut self.listing_state else {
+ return;
+ };
+
+ while self.mqtt.client().can_publish(QoS::AtLeastOnce) {
+ // Note(unwrap): Publishing should not fail because `can_publish()` was checked before
+ // attempting this publish.
+ let (code, path) = iter
+ .next()
+ .map(|path| (ResponseCode::Continue, path.unwrap()))
+ .unwrap_or((ResponseCode::Ok, String::new()));
+
+ let props = [code.as_user_property()];
+ let outgoing = Publication::new(path.as_bytes())
+ .topic(&cache.topic)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce);
+
+ let outgoing = if let Some(cd) = &cache.correlation_data {
+ outgoing.correlate(cd)
+ } else {
+ outgoing
+ };
+
+ let publication = match outgoing.finish() {
+ Ok(response) => response,
+ Err(e) => {
+ // Something went wrong. Abort the listing.
+ log::error!("Listing failed to build response: {e:?}");
+ self.listing_state.take();
+ return;
+ }
+ };
+
+ // Note(unwrap) We already checked that we can publish earlier.
+ self.mqtt.client().publish(publication).unwrap();
+
+ // If we're done with listing, bail out of the loop.
+ if code != ResponseCode::Continue {
+ self.listing_state.take();
+ break;
+ }
+ }
+ }
+
+ fn handle_republish(&mut self) {
+ while self.mqtt.client().can_publish(QoS::AtMostOnce) {
+ let Some(topic) = self.state.context_mut().republish_state.next() else {
+ // If we got here, we completed iterating over the topics and published them all.
+ self.state.process_event(sm::Events::Complete).unwrap();
+ break;
+ };
+
+ let topic = topic.unwrap();
+
+ let mut prefixed_topic = self.prefix.clone();
+ prefixed_topic
+ .push_str("/settings")
+ .and_then(|_| prefixed_topic.push_str(&topic))
+ .unwrap();
+
+ // Note(unwrap): This should not fail because `can_publish()` was checked before
+ // attempting this publish.
+ match self.mqtt.client().publish(
+ // If the topic is not present, we'll fail to serialize the setting into the
+ // payload and will never publish. The iterator has already incremented, so this is
+ // acceptable.
+ DeferredPublication::new(|buf| self.settings.get_json(&topic, buf))
+ .topic(&prefixed_topic)
+ .finish()
+ .unwrap(),
+ ) {
+ Err(minimq::PubError::Serialization(Error::Absent(_))) => {}
+
+ // If the value is too large to serialize, print an error to the topic instead
+ Err(minimq::PubError::Error(minimq::Error::Minimq(
+ minimq::MinimqError::Protocol(minimq::ProtocolError::Serialization(
+ minimq::SerError::InsufficientMemory,
+ )),
+ ))) => {
+ self.mqtt
+ .client()
+ .publish(
+ Publication::new(b"<error: serialization too large>")
+ .topic(&prefixed_topic)
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .finish()
+ .unwrap(),
+ )
+ .unwrap();
+ }
+ other => other.unwrap(),
+ }
+ }
+ }
+
+ fn handle_subscription(&mut self) {
+ log::info!("MQTT connected, subscribing to settings");
+
+ // Note(unwrap): We construct a string with two more characters than the prefix
+ // structure, so we are guaranteed to have space for storage.
+ let mut settings_topic = self.prefix.clone();
+ settings_topic.push_str("/settings/#").unwrap();
+ let mut list_topic = self.prefix.clone();
+ list_topic.push_str("/list").unwrap();
+
+ let opts = SubscriptionOptions::default().ignore_local_messages();
+ let topics = [
+ TopicFilter::new(&settings_topic).options(opts),
+ TopicFilter::new(&list_topic).options(opts),
+ ];
+
+ if self.mqtt.client().subscribe(&topics, &[]).is_ok() {
+ self.state.process_event(sm::Events::Subscribed).unwrap();
+ }
+ }
+
+ fn handle_indicating_alive(&mut self) {
+ // Publish a connection status message.
+ let mut connection_topic = self.prefix.clone();
+ connection_topic.push_str("/alive").unwrap();
+
+ if self
+ .mqtt
+ .client()
+ .publish(
+ Publication::new(b"1")
+ .topic(&connection_topic)
+ .retain()
+ .finish()
+ .unwrap(),
+ )
+ .is_ok()
+ {
+ self.state.process_event(sm::Events::IndicatedLife).unwrap();
+ }
+ }
+
+ /// Update the MQTT interface and service the network. Pass any settings changes to the handler
+ /// supplied.
+ ///
+ /// # Args
+ /// * `handler` - A closure called with updated settings that can be used to apply current
+ /// settings or validate the configuration. Arguments are (path, old_settings, new_settings).
+ ///
+ /// # Returns
+ /// True if the settings changed. False otherwise.
+ pub fn handled_update<F, E>(&mut self, handler: F) -> Result<bool, minimq::Error<Stack::Error>>
+ where
+ F: FnMut(&str, &mut Settings, &Settings) -> Result<(), E>,
+ E: core::fmt::Display,
+ {
+ if !self.mqtt.client().is_connected() {
+ // Note(unwrap): It's always safe to reset.
+ self.state.process_event(sm::Events::Reset).unwrap();
+ }
+
+ match *self.state.state() {
+ sm::States::Initial => {
+ if self.mqtt.client().is_connected() {
+ self.state.process_event(sm::Events::Connected).unwrap();
+ }
+ }
+ sm::States::ConnectedToBroker => self.handle_indicating_alive(),
+ sm::States::PendingSubscribe => self.handle_subscription(),
+ sm::States::PendingRepublish => {
+ if self.state.context().republish_has_timed_out() {
+ self.state
+ .process_event(sm::Events::StartRepublish)
+ .unwrap();
+ }
+ }
+ sm::States::RepublishingSettings => self.handle_republish(),
+
+ // Nothing to do in the active state.
+ sm::States::Active => {}
+ }
+
+ self.handle_listing();
+
+ // All states must handle MQTT traffic.
+ self.handle_mqtt_traffic(handler)
+ }
+
+ fn handle_mqtt_traffic<F, E>(
+ &mut self,
+ mut handler: F,
+ ) -> Result<bool, minimq::Error<Stack::Error>>
+ where
+ F: FnMut(&str, &mut Settings, &Settings) -> Result<(), E>,
+ E: core::fmt::Display,
+ {
+ let mut updated = false;
+ match self.mqtt.poll(|client, topic, message, properties| {
+ let Some(path) = topic.strip_prefix(self.prefix.as_str()) else {
+ log::info!("Unexpected topic prefix: {topic}");
+ return;
+ };
+
+ let Ok(command) = Command::from_message(path, message) else {
+ log::info!("Unknown Miniconf command: {path}");
+ return;
+ };
+
+ match command {
+ Command::List => {
+ if !properties
+ .into_iter()
+ .any(|prop| matches!(prop, Ok(minimq::Property::ResponseTopic(_))))
+ {
+ log::info!("Discarding `List` without `ResponseTopic`");
+ return;
+ }
+
+ let response = match self.listing_state {
+ Some(_) => "`List` already in progress",
+ None => {
+ match handle_listing_request(properties) {
+ Err(msg) => msg,
+ Ok(cache) => {
+ self.listing_state
+ .replace((cache, Settings::iter_paths_unchecked("/")));
+
+ // There is no positive response sent during list commands,
+ // instead, the response is sent as a property of the listed
+ // elements. As such, we are now finished processing a list
+ // command.
+ return;
+ }
+ }
+ }
+ };
+
+ let props = [ResponseCode::Error.as_user_property()];
+ if let Ok(response) = minimq::Publication::new(response.as_bytes())
+ .reply(properties)
+ .properties(&props)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+
+ Command::Get { path } => {
+ let props = [ResponseCode::Ok.as_user_property()];
+ let Ok(message) =
+ DeferredPublication::new(|buf| self.settings.get_json(path, buf))
+ .properties(&props)
+ .reply(properties)
+ // Override the response topic with the path.
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ else {
+ // If we can't create the publication, it's because there's no way to reply
+ // to the message. Since we don't know where to send things, abort now and
+ // complete handling of the `Get` request.
+ return;
+ };
+
+ if let Err(minimq::PubError::Serialization(err)) = client.publish(message) {
+ if let Ok(message) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ // Try to send the error as a best-effort. If we don't have enough
+ // buffer space to encode the error, there's nothing more we can do.
+ client.publish(message).ok();
+ };
+ }
+ }
+
+ Command::Set { path, value } => {
+ let mut new_settings = self.settings.clone();
+ if let Err(err) = new_settings.set_json(path, value) {
+ if let Ok(response) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ return;
+ };
+
+ updated = true;
+
+ match handler(path, &mut self.settings, &new_settings) {
+ Ok(_) => {
+ if let Ok(response) = Publication::new("OK".as_bytes())
+ .properties(&[ResponseCode::Ok.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+ Err(err) => {
+ if let Ok(response) = DeferredPublication::new(|mut buf| {
+ let start = buf.len();
+ write!(buf, "{}", err).and_then(|_| Ok(start - buf.len()))
+ })
+ .properties(&[ResponseCode::Error.as_user_property()])
+ .reply(properties)
+ .qos(QoS::AtLeastOnce)
+ .finish()
+ {
+ client.publish(response).ok();
+ }
+ }
+ }
+ }
+ }
+ }) {
+ Ok(_) => Ok(updated),
+ Err(minimq::Error::SessionReset) => {
+ log::warn!("Session reset");
+ self.state.process_event(sm::Events::Reset).unwrap();
+ Ok(false)
+ }
+ Err(other) => Err(other),
+ }
+ }
+
+ /// Update the settings from the network stack without any specific handling.
+ ///
+ /// # Returns
+ /// True if the settings changed. False otherwise
+ pub fn update(&mut self) -> Result<bool, minimq::Error<Stack::Error>> {
+ self.handled_update(|_, old, new| {
+ *old = new.clone();
+ Result::<_, &'static str>::Ok(())
+ })
+ }
+
+ /// Get the current settings from miniconf.
+ pub fn settings(&self) -> &Settings {
+ &self.settings
+ }
+
+ /// Force republication of the current settings.
+ ///
+ /// # Note
+ /// This is intended to be used if modification of a setting had side effects that affected
+ /// another setting.
+ pub fn force_republish(&mut self) {
+ self.state.process_event(sm::Events::StartRepublish).ok();
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+enum ResponseCode {
+ Ok,
+ Continue,
+ Error,
+}
+
+impl ResponseCode {
+ fn as_user_property(self) -> minimq::Property<'static> {
+ let string = match self {
+ ResponseCode::Ok => "Ok",
+ ResponseCode::Continue => "Continue",
+ ResponseCode::Error => "Error",
+ };
+
+ minimq::Property::UserProperty(
+ minimq::types::Utf8String("code"),
+ minimq::types::Utf8String(string),
+ )
+ }
+}
+
+fn handle_listing_request(
+ properties: &minimq::types::Properties<'_>,
+) -> Result<ListCache, &'static str> {
+ // If the response topic is too long, send an error
+ let response_topic = properties.into_iter().response_topic().unwrap();
+
+ // If there is a CD and it's too long, send an error response.
+ let correlation_data = if let Some(cd) = properties.into_iter().find_map(|prop| {
+ if let Ok(minimq::Property::CorrelationData(cd)) = prop {
+ Some(cd.0)
+ } else {
+ None
+ }
+ }) {
+ Some(Vec::try_from(cd).map_err(|_| "Correlation data too long")?)
+ } else {
+ None
+ };
+
+ Ok(ListCache {
+ topic: String::try_from(response_topic).map_err(|_| "Response topic too long")?,
+ correlation_data,
+ })
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +
use crate::{Error, Key, Metadata, TreeDeserialize, TreeKey, TreeSerialize};
+use serde::{de::Deserialize, Deserializer, Serialize, Serializer};
+
+// `Option` does not add to the path hierarchy (does not consume from `keys` or call `func`).
+// But it does add one Tree API layer between its `Tree<Y>` level
+// and its inner type `Tree<Y'>` level: `Y' = Y - 1`.
+
+// the Y >= 2 cases:
+macro_rules! depth {
+ ($($y:literal)+) => {$(
+ impl<T: TreeKey<{$y - 1}>> TreeKey<$y> for Option<T> {
+ fn name_to_index(_value: &str) -> Option<usize> {
+ None
+ }
+
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ T::traverse_by_key(keys, func)
+ }
+
+ fn metadata() -> Metadata {
+ T::metadata()
+ }
+ }
+
+ impl<T: TreeSerialize<{$y - 1}>> TreeSerialize<$y> for Option<T> {
+ fn serialize_by_key<K, S>(&self, keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer,
+ {
+ if let Some(inner) = self {
+ inner.serialize_by_key(keys, ser)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+ }
+
+ impl<'de, T: TreeDeserialize<'de, {$y - 1}>> TreeDeserialize<'de, $y> for Option<T> {
+ fn deserialize_by_key<K, D>(&mut self, keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>,
+ {
+ if let Some(inner) = self {
+ inner.deserialize_by_key(keys, de)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+ }
+ )+}
+}
+depth!(2 3 4 5 6 7 8);
+
+// Y == 1
+impl<T> TreeKey for Option<T> {
+ fn name_to_index(_value: &str) -> Option<usize> {
+ None
+ }
+
+ fn traverse_by_key<K, F, E>(_keys: K, _func: F) -> Result<usize, Error<E>>
+ where
+ F: FnMut(usize, &str) -> Result<(), E>,
+ {
+ Ok(0)
+ }
+
+ fn metadata() -> Metadata {
+ Metadata {
+ count: 1,
+ ..Default::default()
+ }
+ }
+}
+
+impl<T: Serialize> TreeSerialize for Option<T> {
+ fn serialize_by_key<K, S>(&self, mut keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ S: Serializer,
+ {
+ if keys.next().is_some() {
+ Err(Error::TooLong(0))
+ } else if let Some(inner) = self {
+ inner.serialize(ser)?;
+ Ok(0)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+}
+
+impl<'de, T: Deserialize<'de>> TreeDeserialize<'de> for Option<T> {
+ fn deserialize_by_key<K, D>(&mut self, mut keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ D: Deserializer<'de>,
+ {
+ if keys.next().is_some() {
+ Err(Error::TooLong(0))
+ } else if let Some(inner) = self {
+ *inner = T::deserialize(de)?;
+ Ok(0)
+ } else {
+ Err(Error::Absent(0))
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +
use crate::PathIter;
+use core::fmt::{Display, Formatter, Write};
+use serde::{Deserializer, Serializer};
+
+/// Errors that can occur when using the Tree traits.
+///
+/// A `usize` member indicates the key depth where the error occurred.
+/// The depth here is the number of names or indices consumed.
+/// It is also the number of separators in a path or the length
+/// of an indices slice.
+///
+/// If multiple errors are applicable simultaneously the precedence
+/// is from high to low:
+///
+/// `Absent > TooShort > NotFound > TooLong > Inner > PostDeserialization`
+/// before any `Ok`.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Error<E> {
+ /// The key is valid, but does not exist at runtime.
+ ///
+ /// This is the case if an [`Option`] using the `Tree*` traits
+ /// is `None` at runtime. See also [`TreeKey#option`].
+ Absent(usize),
+
+ /// The key ends early and does not reach a leaf node.
+ TooShort(usize),
+
+ /// The key was not found (index unparsable or too large, name not found or invalid).
+ NotFound(usize),
+
+ /// The key is too long and goes beyond a leaf node.
+ TooLong(usize),
+
+ /// The value provided could not be serialized or deserialized
+ /// or the traversal function returned an error.
+ Inner(E),
+
+ /// There was an error after deserializing a value.
+ ///
+ /// The `Deserializer` has encountered an error only after successfully
+ /// deserializing a value. This is the case if there is additional unexpected data.
+ /// The [`TreeDeserialize::deserialize_by_key()`] update takes place but this
+ /// error will be returned.
+ PostDeserialization(E),
+}
+
+impl<E: core::fmt::Display> Display for Error<E> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Error::Absent(index) => {
+ write!(f, "Path is not currently available (Key level: {})", index)
+ }
+ Error::TooShort(index) => {
+ write!(f, "Provided path was too short (Key level: {})", index)
+ }
+ Error::NotFound(index) => {
+ write!(f, "The provided path was not found (Key level: {})", index)
+ }
+ Error::TooLong(index) => {
+ write!(f, "The provided path was too long (Key level: {})", index)
+ }
+ Error::Inner(error) => {
+ write!(f, "Value could not be (de)serialized: ")?;
+ error.fmt(f)
+ }
+ Error::PostDeserialization(error) => {
+ write!(f, "Error after deserialization: ")?;
+ error.fmt(f)
+ }
+ }
+ }
+}
+
+impl<T> From<T> for Error<T> {
+ fn from(value: T) -> Self {
+ Error::Inner(value)
+ }
+}
+
+/// Pass a [`Result`] up one hierarchy level, incrementing its usize member.
+pub trait Increment {
+ /// Increment the `depth` member by one.
+ fn increment(self) -> Self;
+}
+
+impl<E> Increment for Result<usize, Error<E>> {
+ fn increment(self) -> Self {
+ match self {
+ Ok(i) => Ok(i + 1),
+ Err(Error::NotFound(i)) => Err(Error::NotFound(i + 1)),
+ Err(Error::TooShort(i)) => Err(Error::TooShort(i + 1)),
+ Err(Error::TooLong(i)) => Err(Error::TooLong(i + 1)),
+ Err(Error::Absent(i)) => Err(Error::Absent(i + 1)),
+ e => e,
+ }
+ }
+}
+
+/// Unit struct to indicate a short indices iterator in [`TreeKey::indices()`].
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct SliceShort;
+
+/// Metadata about a [TreeKey] namespace.
+#[non_exhaustive]
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Metadata {
+ /// The maximum length of a path in bytes.
+ ///
+ /// This is the concatenation of all names in a path
+ /// and does not include separators.
+ /// It includes paths that may be [`Error::Absent`] at runtime.
+ pub max_length: usize,
+
+ /// The maximum path depth.
+ ///
+ /// This is equal to the maximum number of path hierarchy separators.
+ /// It may be smaller than the [Tree recursion depth const generic paramerter `Y`](TreeKey#recursion).
+ /// It includes paths that may be [`Error::Absent`] at runtime.
+ pub max_depth: usize,
+
+ /// The total number of paths.
+ ///
+ /// This includes paths that may be [`Error::Absent`] at runtime.
+ pub count: usize,
+}
+
+impl Metadata {
+ /// Add separator length to the maximum path length.
+ ///
+ /// To obtain an upper bound on the maximum length of all paths
+ /// including separators, this adds `max_depth*separator_length`.
+ pub fn separator(self, separator: &str) -> Self {
+ Self {
+ max_length: self.max_length + self.max_depth * separator.len(),
+ ..self
+ }
+ }
+}
+
+/// Capability to convert a key into a node index for a given `M: TreeKey`.
+pub trait Key {
+ /// Convert the key `self` to a `usize` index.
+ fn find<const Y: usize, M: TreeKey<Y>>(&self) -> Option<usize>;
+}
+
+// `usize` index as Key
+impl Key for usize {
+ #[inline]
+ fn find<const Y: usize, M>(&self) -> Option<usize> {
+ Some(*self)
+ }
+}
+
+// &str name as Key
+impl Key for &str {
+ #[inline]
+ fn find<const Y: usize, M: TreeKey<Y>>(&self) -> Option<usize> {
+ M::name_to_index(self)
+ }
+}
+
+/// Traversal, iteration, and serialization/deserialization of nodes in a tree.
+///
+/// The following documentation sections on `TreeKey<Y>` apply analogously to `TreeSerialize<Y>`
+/// and `TreeDeserialize<Y>`.
+///
+/// # Recursion
+///
+/// The `TreeKey` trait (and the `TreeSerialize`/`TreeDeserialize` traits as well)
+/// are meant to be implemented
+/// recursively on nested data structures. Recursion here means that a container
+/// that implements `TreeKey`, may call on the `TreeKey` implementations of
+/// inner types.
+///
+/// The const parameter `Y` in the traits here is the recursion depth and determines the
+/// maximum nesting of `TreeKey` layers. It's at least `1` and defaults to `1`.
+///
+/// The recursion depth `Y` doubles as an upper bound to the key length
+/// (the depth/height of the tree):
+/// An implementor of `TreeKey<Y>` may consume at most `Y` items from the
+/// `keys` iterator argument in the recursive methods ([`TreeSerialize::serialize_by_key()`],
+/// [`TreeDeserialize::deserialize_by_key()`], [`TreeKey::traverse_by_key()`]). This includes
+/// both the items consumed directly before recursing and those consumed indirectly
+/// by recursing into inner types. In the same way it may call `func` in
+/// [`TreeKey::traverse_by_key()`] at most `Y` times, again including those calls due
+/// to recursion into inner `Miniconf` types.
+///
+/// This implies that if an implementor `T` of `TreeKey<Y>` (with `Y >= 1`) contains and recurses into
+/// an inner type using that type's `TreeKey<Z>` implementation, then `1 <= Z <= Y` must
+/// hold and `T` may consume at most `Y - Z` items from the `keys` iterators and call
+/// `func` at most `Y - Z` times.
+///
+/// # Keys
+///
+/// The keys used to locate nodes can be either iterators over `usize` or iterators
+/// over `&str` names.
+///
+/// `usize` may appear like ASN.1 Object Identifiers.
+/// `&str` keys are sequences of names, like path names. When concatenated, they are separated by
+/// path hierarchy separators, e.g. `'/'`.
+///
+/// # Derive macros
+///
+/// Derive macros to automatically implement the correct `TreeKey<Y>` traits on a struct `S` are available through
+/// [`macro@crate::TreeKey`], [`macro@crate::TreeSerialize`], and [`macro@crate::TreeDeserialize`].
+/// A shorthand derive macro that derives all three trait implementations is also available at [`macro@crate::Tree`].
+///
+/// To derive `TreeSerialize`/`TreeDeserialize`, each field in the struct must either implement
+/// [`serde::Serialize`]/[`serde::de::DeserializeOwned`]
+/// (and ultimately also be supported by the intended [`serde::Serializer`]/[`serde::Deserializer`] backend)
+/// or implement the respective `TreeSerialize`/`TreeDeserialize` trait themselves for the required remaining
+/// recursion depth.
+///
+/// For each field, the remaining recursion depth is configured through the `#[tree(depth(Y))]`
+/// attribute, with `Y = 1` being the implied default when using `#[tree()]` and `Y = 0` invalid.
+/// If the attribute is not present, the field is a leaf and accessed only through its
+/// [`serde::Serialize`]/[`serde::Deserialize`] implementation.
+/// With the attribute present the field is accessed through its `TreeKey<Y>` implementation with the given
+/// remaining recursion depth.
+///
+/// # Array
+///
+/// Blanket implementations of the `TreeKey` traits are provided for homogeneous arrays [`[T; N]`](core::array)
+/// up to recursion depth `Y = 8`.
+///
+/// When a [`[T; N]`](core::array) is used as `TreeKey<Y>` (i.e. marked as `#[tree(depth(Y))]` in a struct)
+/// and `Y > 1` each item of the array is accessed as a `TreeKey` tree.
+/// For a depth `Y = 0` (attribute absent), the entire array is accessed as one atomic
+/// value. For `Y = 1` each index of the array is is instead accessed as
+/// one atomic value.
+///
+/// The type to use depends on the desired semantics of the data contained in the array. If the array
+/// contains `TreeKey` items, one can (and often wants to) use `Y >= 2`.
+/// However, if each element in the array should be individually configurable as a single value (e.g. a list
+/// of `u32`), then `Y = 1` can be used. With `Y = 0` all items are to be accessed simultaneously and atomically.
+/// For e.g. `[[T; 2]; 3] where T: TreeKey<3>` the recursion depth is `Y = 5`. It automatically implements
+/// `TreeKey<5>`.
+/// For `[[T; 2]; 3] where T: Serialize + DeserializeOwned`, any `Y <= 2` is available.
+///
+/// # Option
+///
+/// Blanket implementations of the `TreeKey` traits are provided for [`Option<T>`]
+/// up to recursion depth `Y = 8`.
+///
+/// These implementation do not alter the path hierarchy and do not consume any items from the `keys`
+/// iterators. The `TreeKey` behavior of an [`Option`] is such that the `None` variant makes the corresponding part
+/// of the tree inaccessible at run-time. It will still be iterated over by [`TreeKey::iter_paths()`] but attempts
+/// to [`TreeSerialize::serialize_by_key()`] or [`TreeDeserialize::deserialize_by_key()`] them
+/// return [`Error::Absent`].
+/// This is intended as a mechanism to provide run-time construction of the namespace. In some
+/// cases, run-time detection may indicate that some component is not present. In this case,
+/// namespaces will not be exposed for it.
+///
+/// If the depth specified by the `#[tree(depth(Y))]` attribute exceeds 1,
+/// the `Option` can be used to access within the inner type using its `TreeKey` trait.
+/// If there is no `tree` attribute on an `Option` field in a `struct or in an array,
+/// JSON `null` corresponds to `None` as usual and the `TreeKey` trait is not used.
+///
+/// The following example shows potential usage of arrays and `Option`:
+///
+/// ```
+/// # use miniconf::TreeKey;
+/// #[derive(TreeKey)]
+/// struct S {
+/// // "/b/1/2" = 5
+/// #[tree(depth(2))]
+/// b: [[u32; 3]; 3],
+/// // "/c/0" = [3,4], optionally absent at runtime
+/// #[tree(depth(2))]
+/// c: [Option<[u32; 2]>; 2],
+/// }
+/// ```
+///
+/// ## Generics
+///
+/// The macros add bounds to generic types of the struct they are acting on.
+/// If a generic type parameter `T` of the struct `S<T>`is used as a type parameter to a
+/// field type `a: F1<F2<T>>` the type `T` will be considered to reside at type depth `X = 2` (as it is
+/// within `F2` which is within `F1`) and the following bounds will be applied:
+///
+/// * With the `#[tree()]` attribute not present on `a`, `T` will receive bounds `Serialize`/`DeserializeOwned` when
+/// `TreeSerialize`/`TreeDeserialize` is derived.
+/// * With `#[tree(depth(Y))]`, and `Y - X < 1` it will also receive bounds `Serialize + DeserializeOwned`.
+/// * For `Y - X >= 1` it will receive the bound `T: TreeKey<Y - X>`.
+///
+/// E.g. In the following `T` resides at depth `2` and `T: TreeKey<1>` will be inferred:
+///
+/// ```
+/// # use miniconf::TreeKey;
+/// #[derive(TreeKey)]
+/// struct S<T> {
+/// #[tree(depth(3))]
+/// a: [Option<T>; 2],
+/// };
+/// // This works as [u32; N] implements TreeKey<1>:
+/// S::<[u32; 5]>::metadata();
+/// // This does not compile as u32 does not implement TreeKey<1>:
+/// // S::<u32>::metadata();
+/// ```
+///
+/// This behavior is upheld by and compatible with all implementations in this crate. It is only violated
+/// when deriving `TreeKey` for a struct that (a) forwards its own type parameters as type
+/// parameters to its field types, (b) uses `TreeKey` on those fields, and (c) those field
+/// types use their type parameters at other levels than `TreeKey<Y - 1>`. See the
+/// `test_derive_macro_bound_failure` test in `tests/generics.rs`.
+///
+/// # Example
+///
+/// See the [`crate`] documentation for an example showing how the traits and the derive macros work.
+pub trait TreeKey<const Y: usize = 1> {
+ /// Convert a node name to a node index.
+ ///
+ /// The details of the mapping and the `usize` index values
+ /// are an implementation detail and only need to be stable for at runtime.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// }
+ /// assert_eq!(S::name_to_index("bar"), Some(1));
+ /// ```
+ fn name_to_index(name: &str) -> Option<usize>;
+
+ /// Call a function for each node on the path described by keys.
+ ///
+ /// Traversal is aborted once `func` returns an `Err(E)`.
+ ///
+ /// May not exhaust `keys` if a leaf is found early. i.e. `keys`
+ /// may be longer than required.
+ /// If `Self` is a leaf, nothing will be consumed from `keys`
+ /// and `Ok(0)` will be returned.
+ /// If `Self` is non-leaf (internal node) and the iterator is
+ /// exhausted (empty),
+ /// `Err(Error::TooShort(0))` will be returned.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// assert_eq!(
+ /// S::traverse_by_key(["bar"].into_iter(), |index, name| {
+ /// assert_eq!((1, "bar"), (index, name));
+ /// Ok::<_, ()>(())
+ /// }),
+ /// Ok(1)
+ /// );
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `func`: A `FnMut` to be called for each node on the path. Its arguments are
+ /// the index and the name of the node at the given depth. Returning `Err()` aborts
+ /// the traversal.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn traverse_by_key<K, F, E>(keys: K, func: F) -> Result<usize, Error<E>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ // Writing this to return an iterator instead of using a callback
+ // would have worse performance (O(n^2) instead of O(n) for matching)
+ F: FnMut(usize, &str) -> Result<(), E>;
+
+ /// Get metadata about the paths in the namespace.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let m = S::metadata();
+ /// assert_eq!((m.max_depth, m.max_length, m.count), (1, 3, 2));
+ /// ```
+ fn metadata() -> Metadata;
+
+ /// Convert keys to path.
+ ///
+ /// This is typically called through a [PathIter] returned by [TreeKey::iter_paths].
+ ///
+ /// `keys` may be longer than required. Extra items are ignored.
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut s = String::new();
+ /// S::path([1], &mut s, "/").unwrap();
+ /// assert_eq!(s, "/bar");
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `path`: A string to write the separators and node names into.
+ /// See also [TreeKey::metadata()] for upper bounds on path length.
+ /// * `sep`: The path hierarchy separator to be inserted before each name.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn path<K, P>(keys: K, mut path: P, sep: &str) -> Result<usize, Error<core::fmt::Error>>
+ where
+ K: IntoIterator,
+ K::Item: Key,
+ P: Write,
+ {
+ Self::traverse_by_key(keys.into_iter(), |_index, name| {
+ path.write_str(sep).and_then(|_| path.write_str(name))
+ })
+ }
+
+ /// Convert keys to `indices`.
+ ///
+ /// This determines the `indices` of the item specified by `keys`.
+ ///
+ /// See also [`TreeKey::path()`] for the analogous function.
+ ///
+ /// Entries in `indices` at and beyond the `depth` returned are unaffected.
+ ///
+ /// ```
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut i = [0; 2];
+ /// let depth = S::indices(["bar"], &mut i).unwrap();
+ /// assert_eq!(&i[..depth], &[1]);
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `indices`: An iterator of `&mut usize` to write the node indices into.
+ /// If `indices` is shorter than the node depth, [`Error<SliceShort>`] is returned
+ /// See also [TreeKey::metadata()] for upper bounds on depth.
+ ///
+ /// # Returns
+ /// Final node depth on success
+ fn indices<'a, K, I>(keys: K, indices: I) -> Result<usize, Error<SliceShort>>
+ where
+ K: IntoIterator,
+ K::Item: Key,
+ I: IntoIterator<Item = &'a mut usize>,
+ {
+ let mut indices = indices.into_iter();
+ Self::traverse_by_key(keys.into_iter(), |index, _name| {
+ let idx = indices.next().ok_or(SliceShort)?;
+ *idx = index;
+ Ok(())
+ })
+ }
+
+ /// Create an iterator of all possible paths.
+ ///
+ /// This is a depth-first walk.
+ /// The iterator will walk all paths, including those that may be absent at
+ /// run-time (see [Option]).
+ /// The iterator has an exact and trusted [Iterator::size_hint].
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let paths: Vec<String> = S::iter_paths("/").map(|p| p.unwrap()).collect();
+ /// assert_eq!(paths, ["/foo", "/bar"]);
+ /// # }
+ /// ```
+ ///
+ /// # Generics
+ /// * `P` - The type to hold the path. Needs to be `core::fmt::Write + Default`
+ ///
+ /// # Args
+ /// * `sep` - The path hierarchy separator
+ ///
+ /// # Returns
+ /// An iterator of paths with a trusted and exact [`Iterator::size_hint()`].
+ #[inline]
+ fn iter_paths<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> {
+ PathIter::new(sep)
+ }
+
+ /// Create an unchecked iterator of all possible paths.
+ ///
+ /// See also [TreeKey::iter_paths].
+ ///
+ /// ```
+ /// # #[cfg(feature = "std")] {
+ /// # use miniconf::TreeKey;
+ /// #[derive(TreeKey)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let paths: Vec<String> = S::iter_paths_unchecked("/").map(|p| p.unwrap()).collect();
+ /// assert_eq!(paths, ["/foo", "/bar"]);
+ /// # }
+ /// ```
+ ///
+ /// # Generics
+ /// * `P` - The type to hold the path. Needs to be `core::fmt::Write + Default`.
+ ///
+ /// # Args
+ /// * `sep` - The path hierarchy separator
+ ///
+ /// # Returns
+ /// A iterator of paths.
+ #[inline]
+ fn iter_paths_unchecked<P: Write>(sep: &str) -> PathIter<'_, Self, Y, P> {
+ PathIter::new_unchecked(sep)
+ }
+}
+
+/// Serialize a leaf node by its keys.
+///
+/// See also [`crate::JsonCoreSlash`] for a convenient blanket implementation using this trait.
+pub trait TreeSerialize<const Y: usize = 1>: TreeKey<Y> {
+ /// Serialize a node by keys.
+ ///
+ /// ```
+ /// # #[cfg(feature = "json-core")] {
+ /// # use miniconf::{TreeSerialize, TreeKey};
+ /// #[derive(TreeKey, TreeSerialize)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let s = S { foo: 9, bar: 11 };
+ /// let mut buf = [0u8; 10];
+ /// let mut ser = serde_json_core::ser::Serializer::new(&mut buf);
+ /// s.serialize_by_key(["bar"].into_iter(), &mut ser).unwrap();
+ /// let length = ser.end();
+ /// assert_eq!(&buf[..length], b"11");
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `ser`: A `Serializer` to to serialize the value.
+ ///
+ /// # Returns
+ /// Node depth on success.
+ fn serialize_by_key<K, S>(&self, keys: K, ser: S) -> Result<usize, Error<S::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ S: Serializer;
+}
+
+/// Deserialize a leaf node by its keys.
+///
+/// See also [`crate::JsonCoreSlash`] for a convenient blanket implementation using this trait.
+pub trait TreeDeserialize<'de, const Y: usize = 1>: TreeKey<Y> {
+ /// Deserialize an node by keys.
+ ///
+ /// ```
+ /// # #[cfg(feature = "json-core")] {
+ /// # use miniconf::{TreeDeserialize, TreeKey};
+ /// #[derive(TreeKey, TreeDeserialize)]
+ /// struct S {
+ /// foo: u32,
+ /// bar: u16,
+ /// };
+ /// let mut s = S { foo: 9, bar: 11 };
+ /// let mut de = serde_json_core::de::Deserializer::new(b"7");
+ /// s.deserialize_by_key(["bar"].into_iter(), &mut de).unwrap();
+ /// de.end().unwrap();
+ /// assert_eq!(s.bar, 7);
+ /// # }
+ /// ```
+ ///
+ /// # Args
+ /// * `keys`: An `Iterator` of `Key`s identifying the node.
+ /// * `de`: A `Deserializer` to deserialize the value.
+ ///
+ /// # Returns
+ /// Node depth on success
+ fn deserialize_by_key<K, D>(&mut self, keys: K, de: D) -> Result<usize, Error<D::Error>>
+ where
+ K: Iterator,
+ K::Item: Key,
+ D: Deserializer<'de>;
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +
//! Stabilizer ADC management interface
+//!
+//! # Design
+//!
+//! Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs
+//! require a setup conversion time after asserting the CSn (convert) signal to generate the ADC
+//! code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of
+//! MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter
+//! during SPI configuration, which allows offloading the management of the setup time to hardware.
+//!
+//! Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate
+//! the collection of multiple ADC samples without requiring processing by the CPU, which reduces
+//! overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
+//!
+//! The automation of sample collection utilizes three DMA streams, the SPI peripheral, and two
+//! timer compare channel for each ADC. One timer comparison channel is configured to generate a
+//! comparison event every time the timer is equal to a specific value. Each comparison then
+//! generates a DMA transfer event to write into the SPI CR1 register to initiate the transfer.
+//! This allows the SPI interface to periodically read a single sample. The other timer comparison
+//! channel is configured to generate a comparison event slightly before the first (~10 timer
+//! cycles). This channel triggers a separate DMA stream to clear the EOT flag within the SPI
+//! peripheral. The EOT flag must be cleared after each transfer or the SPI peripheral will not
+//! properly complete the single conversion. Thus, by using two DMA streams and timer comparison
+//! channels, the SPI can regularly acquire ADC samples.
+//!
+//! In order to collect the acquired ADC samples into a RAM buffer, a final DMA transfer is
+//! configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to
+//! the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is
+//! available. When enough samples have been collected, a transfer-complete interrupt is generated
+//! and the ADC samples are available for processing.
+//!
+//! After a complete transfer of a batch of samples, the inactive buffer is available to the
+//! user for processing. The processing must complete before the DMA transfer of the next batch
+//! completes.
+//!
+//! ## Starting Data Collection
+//!
+//! Because the DMA data collection is automated via timer count comparisons and DMA transfers, the
+//! ADCs can be initialized and configured, but will not begin sampling the external ADCs until the
+//! sampling timer is enabled. As such, the sampling timer should be enabled after all
+//! initialization has completed and immediately before the embedded processing loop begins.
+//!
+//!
+//! ## Batch Sizing
+//!
+//! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch
+//! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger
+//! batch sizes generally provide for lower overhead and more processing time per sample, but come
+//! at the expense of increased input -> output latency.
+//!
+//!
+//! # Note
+//!
+//! While there are two ADCs, only a single ADC is configured to generate transfer-complete
+//! interrupts. This is done because it is assumed that the ADCs will always be sampled
+//! simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate
+//! transfer-complete interrupts.
+//!
+//! There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As
+//! such, one of the ADCs will be sampled marginally earlier before the other because the DMA
+//! requests are generated simultaneously. This can be avoided by providing a known offset to the
+//! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
+//! value of 0 and ADC1's comparison to a counter value of 1.
+//!
+//! In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs
+//! have finite depth, FIFO access is slower than AXISRAM access, and because the single
+//! buffer mode DMA disable/enable and buffer update sequence is slow.
+use stm32h7xx_hal as hal;
+
+use mutex_trait::Mutex;
+
+use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
+use super::timers;
+
+use hal::{
+ dma::{
+ config::Priority,
+ dma::{DMAReq, DmaConfig},
+ traits::TargetAddress,
+ DMAError, MemoryToPeripheral, PeripheralToMemory, Transfer,
+ },
+ spi::{HalDisabledSpi, HalEnabledSpi, HalSpi},
+};
+
+/// A type representing an ADC sample.
+#[derive(Copy, Clone)]
+pub struct AdcCode(pub u16);
+
+impl AdcCode {
+ // The ADC has a differential input with a range of +/- 4.096 V and 16-bit resolution.
+ // The gain into the two inputs is 1/5.
+ const FULL_SCALE: f32 = 5.0 / 2.0 * 4.096;
+ const VOLT_PER_LSB: f32 = -Self::FULL_SCALE / i16::MIN as f32;
+ const LSB_PER_VOLT: f32 = 1. / Self::VOLT_PER_LSB;
+}
+
+impl From<u16> for AdcCode {
+ /// Construct an ADC code from a provided binary (ADC-formatted) code.
+ fn from(value: u16) -> Self {
+ Self(value)
+ }
+}
+
+impl From<i16> for AdcCode {
+ /// Construct an ADC code from the stabilizer-defined code (i16 full range).
+ fn from(value: i16) -> Self {
+ Self(value as u16)
+ }
+}
+
+impl From<AdcCode> for i16 {
+ /// Get a stabilizer-defined code from the ADC code.
+ fn from(code: AdcCode) -> i16 {
+ code.0 as i16
+ }
+}
+
+impl From<AdcCode> for u16 {
+ /// Get an ADC-frmatted binary value from the code.
+ fn from(code: AdcCode) -> u16 {
+ code.0
+ }
+}
+
+impl From<AdcCode> for f32 {
+ /// Convert raw ADC codes to/from voltage levels.
+ ///
+ /// # Note
+ /// This does not account for the programmable gain amplifier at the signal input.
+ fn from(code: AdcCode) -> f32 {
+ i16::from(code) as f32 * AdcCode::VOLT_PER_LSB
+ }
+}
+
+impl TryFrom<f32> for AdcCode {
+ type Error = ();
+
+ fn try_from(voltage: f32) -> Result<AdcCode, ()> {
+ let code = voltage * Self::LSB_PER_VOLT;
+ if !(i16::MIN as f32..=i16::MAX as f32).contains(&code) {
+ Err(())
+ } else {
+ Ok(AdcCode::from(code as i16))
+ }
+ }
+}
+
+// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the
+// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is
+// initialized during setup.
+#[link_section = ".axisram.buffers"]
+static mut SPI_START: [u32; 1] = [0x00; 1];
+
+// The following data is written by the timer flag clear trigger into the SPI IFCR register to clear
+// the EOT flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This
+// value is initialized during setup.
+#[link_section = ".axisram.buffers"]
+static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
+
+// The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for
+// each transfer in a ping-pong buffer configuration (one is being acquired while the other is being
+// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
+// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
+#[link_section = ".axisram.buffers"]
+static mut ADC_BUF: [[SampleBuffer; 2]; 2] =
+ [[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
+
+macro_rules! adc_input {
+ ($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
+ $spi:ident, $trigger_channel:ident, $dma_req:ident, $clear_channel:ident, $dma_clear_req:ident) => {
+
+ paste::paste! {
+
+ /// $spi-CR is used as a type for indicating a DMA transfer into the SPI control
+ /// register whenever the tim2 update dma request occurs.
+ struct [< $spi CR >] {
+ _channel: timers::tim2::$trigger_channel,
+ }
+ impl [< $spi CR >] {
+ pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
+ Self { _channel }
+ }
+ }
+
+ // Note(unsafe): This structure is only safe to instantiate once. The DMA request is
+ // hard-coded and may only be used if ownership of the timer2 $trigger_channel compare
+ // channel is assured, which is ensured by maintaining ownership of the channel.
+ unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi CR >] {
+
+ type MemSize = u32;
+
+ /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's CR1 to start the
+ /// transfer.
+ fn address(&self) -> usize {
+ // Note(unsafe): It is assumed that SPI is owned by another DMA transfer. This
+ // is only safe because we are writing to a configuration register.
+ let regs = unsafe { &*hal::stm32::$spi::ptr() };
+ ®s.cr1 as *const _ as usize
+ }
+ }
+
+ /// $spi-IFCR is used as a type for indicating a DMA transfer into the SPI flag clear
+ /// register whenever the tim3 compare dma request occurs. The flag must be cleared
+ /// before the transfer starts.
+ struct [< $spi IFCR >] {
+ _channel: timers::tim3::$clear_channel,
+ }
+
+ impl [< $spi IFCR >] {
+ pub fn new(_channel: timers::tim3::$clear_channel) -> Self {
+ Self { _channel }
+ }
+ }
+
+ // Note(unsafe): This structure is only safe to instantiate once. The DMA request is
+ // hard-coded and may only be used if ownership of the timer3 $clear_channel compare
+ // channel is assured, which is ensured by maintaining ownership of the channel.
+ unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi IFCR >] {
+ type MemSize = u32;
+
+ /// SPI DMA requests are generated whenever TIM3 CHx ($dma_clear_req) comparison
+ /// occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_clear_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's IFCR to clear the
+ /// EOT flag to allow the next transmission.
+ fn address(&self) -> usize {
+ // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and
+ // this DMA is only used for writing to the configuration registers.
+ let regs = unsafe { &*hal::stm32::$spi::ptr() };
+ ®s.ifcr as *const _ as usize
+ }
+ }
+
+ /// Represents data associated with ADC.
+ pub struct $name {
+ transfer: Transfer<
+ hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ PeripheralToMemory,
+ &'static mut [u16],
+ hal::dma::DBTransfer,
+ >,
+ trigger_transfer: Transfer<
+ hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
+ [< $spi CR >],
+ MemoryToPeripheral,
+ &'static mut [u32; 1],
+ hal::dma::DBTransfer,
+ >,
+ clear_transfer: Transfer<
+ hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
+ [< $spi IFCR >],
+ MemoryToPeripheral,
+ &'static mut [u32; 1],
+ hal::dma::DBTransfer,
+ >,
+ }
+
+ impl $name {
+ /// Construct the ADC input channel.
+ ///
+ /// # Args
+ /// * `spi` - The SPI interface used to communicate with the ADC.
+ /// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by
+ /// writing a word into the SPI TX FIFO.
+ /// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
+ /// * `clear_stream` - The DMA stream used to clear the EOT flag in the SPI peripheral.
+ /// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers.
+ /// * `clear_channel` - The shadow sampling timer output compare channel used for
+ /// clearing the SPI EOT flag.
+ pub fn new(
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
+ trigger_stream: hal::dma::dma::$trigger_stream<
+ hal::stm32::DMA1,
+ >,
+ data_stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ clear_stream: hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
+ trigger_channel: timers::tim2::$trigger_channel,
+ clear_channel: timers::tim3::$clear_channel,
+ batch_size: usize,
+ ) -> Self {
+ // The flag clear DMA transfer always clears the EOT flag in the SPI
+ // peripheral. It has the highest priority to ensure it is completed before the
+ // transfer trigger.
+ let clear_config = DmaConfig::default()
+ .priority(Priority::VeryHigh)
+ .circular_buffer(true);
+
+ unsafe {
+ SPI_EOT_CLEAR[0] = 1 << 3;
+ }
+
+ // Generate DMA events when the timer hits zero (roll-over). This must be before
+ // the trigger channel DMA occurs, as if the trigger occurs first, the
+ // transmission will not occur.
+ clear_channel.listen_dma();
+ clear_channel.to_output_compare(0);
+
+ let clear_transfer: Transfer<
+ _,
+ _,
+ MemoryToPeripheral,
+ _,
+ _,
+ > = Transfer::init(
+ clear_stream,
+ [< $spi IFCR >]::new(clear_channel),
+ // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is
+ // never actually modified. It technically only needs to be immutably
+ // borrowed, but the current HAL API only supports mutable borrows.
+ unsafe { &mut SPI_EOT_CLEAR },
+ None,
+ clear_config,
+ );
+
+ // Generate DMA events when an output compare of the timer hits the specified
+ // value.
+ trigger_channel.listen_dma();
+ trigger_channel.to_output_compare(2 + $index);
+
+ // The trigger stream constantly writes to the SPI CR1 using a static word
+ // (which is a static value to enable the SPI transfer). Thus, neither the
+ // memory or peripheral address ever change. This is run in circular mode to be
+ // completed at every DMA request.
+ let trigger_config = DmaConfig::default()
+ .priority(Priority::High)
+ .circular_buffer(true);
+
+ // Note(unsafe): This word is initialized once per ADC initialization to verify
+ // it is initialized properly.
+ unsafe {
+ // Write a binary code into the SPI control register to initiate a transfer.
+ SPI_START[0] = 0x201;
+ };
+
+ // Construct the trigger stream to write from memory to the peripheral.
+ let trigger_transfer: Transfer<
+ _,
+ _,
+ MemoryToPeripheral,
+ _,
+ _,
+ > = Transfer::init(
+ trigger_stream,
+ [< $spi CR >]::new(trigger_channel),
+ // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never
+ // actually modified. It technically only needs to be immutably borrowed, but the
+ // current HAL API only supports mutable borrows.
+ unsafe { &mut SPI_START },
+ None,
+ trigger_config,
+ );
+
+ // The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
+ // stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
+ // after the requested number of samples have been collected. Note that only ADC1's (sic!)
+ // data stream is used to trigger a transfer completion interrupt.
+ let data_config = DmaConfig::default()
+ .memory_increment(true)
+ .double_buffer(true)
+ .transfer_complete_interrupt($index == 1)
+ .priority(Priority::VeryHigh);
+
+ // A SPI peripheral error interrupt is used to determine if the RX FIFO
+ // overflows. This indicates that samples were dropped due to excessive
+ // processing time in the main application (e.g. a second DMA transfer completes
+ // before the first was done with processing). This is used as a flow control
+ // indicator to guarantee that no ADC samples are lost.
+ let mut spi = spi.disable();
+ spi.listen(hal::spi::Event::Error);
+
+ // The data transfer is always a transfer of data from the peripheral to a RAM
+ // buffer.
+ let data_transfer: Transfer<_, _, PeripheralToMemory, _, _> =
+ Transfer::init(
+ data_stream,
+ spi,
+ // Note(unsafe): The ADC_BUF[$index] is "owned" by this peripheral.
+ // It shall not be used anywhere else in the module.
+ unsafe { &mut ADC_BUF[$index][0][..batch_size] },
+ unsafe { Some(&mut ADC_BUF[$index][1][..batch_size]) },
+ data_config,
+ );
+
+ Self {
+ transfer: data_transfer,
+ trigger_transfer,
+ clear_transfer,
+ }
+ }
+
+ /// Enable the ADC DMA transfer sequence.
+ pub fn start(&mut self) {
+ self.transfer.start(|spi| {
+ spi.enable_dma_rx();
+
+ spi.inner().cr2.modify(|_, w| w.tsize().bits(1));
+ spi.inner().cr1.modify(|_, w| w.spe().set_bit());
+ });
+
+ self.clear_transfer.start(|_| {});
+ self.trigger_transfer.start(|_| {});
+
+ }
+
+ /// Wait for the transfer of the currently active buffer to complete,
+ /// then call a function on the now inactive buffer and acknowledge the
+ /// transfer complete flag.
+ ///
+ /// NOTE(unsafe): Memory safety and access ordering is not guaranteed
+ /// (see the HAL DMA docs).
+ pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
+ where
+ F: FnOnce(&mut &'static mut [u16]) -> R,
+ {
+ unsafe { self.transfer.next_dbm_transfer_with(|buf, _current| f(buf)) }
+ }
+ }
+
+ // This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
+ // to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
+ impl Mutex for $name {
+ type Data = &'static mut [u16];
+ fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
+ self.with_buffer(f).unwrap()
+ }
+ }
+ }
+ };
+}
+
+adc_input!(
+ Adc0Input, 0, Stream0, Stream1, Stream2, SPI2, Channel1, Tim2Ch1, Channel1,
+ Tim3Ch1
+);
+adc_input!(
+ Adc1Input, 1, Stream3, Stream4, Stream5, SPI3, Channel2, Tim2Ch2, Channel2,
+ Tim3Ch2
+);
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +
use serde::{Deserialize, Serialize};
+
+use core::convert::TryFrom;
+use num_enum::TryFromPrimitive;
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, TryFromPrimitive)]
+#[repr(u8)]
+pub enum Gain {
+ G1 = 0b00,
+ G2 = 0b01,
+ G5 = 0b10,
+ G10 = 0b11,
+}
+
+/// A programmable gain amplifier that allows for setting the gain via GPIO.
+pub struct ProgrammableGainAmplifier<A0, A1> {
+ a0: A0,
+ a1: A1,
+}
+
+impl Gain {
+ /// Get the AFE gain as a numerical value.
+ pub fn as_multiplier(self) -> f32 {
+ match self {
+ Gain::G1 => 1.0,
+ Gain::G2 => 2.0,
+ Gain::G5 => 5.0,
+ Gain::G10 => 10.0,
+ }
+ }
+}
+
+impl<A0, A1> ProgrammableGainAmplifier<A0, A1>
+where
+ A0: embedded_hal::digital::v2::StatefulOutputPin,
+ A0::Error: core::fmt::Debug,
+ A1: embedded_hal::digital::v2::StatefulOutputPin,
+ A1::Error: core::fmt::Debug,
+{
+ /// Construct a new programmable gain driver.
+ ///
+ /// Args:
+ /// * `a0` - An output connected to the A0 input of the amplifier.
+ /// * `a1` - An output connected to the A1 input of the amplifier.
+ pub fn new(a0: A0, a1: A1) -> Self {
+ let mut afe = Self { a0, a1 };
+
+ afe.set_gain(Gain::G1);
+
+ afe
+ }
+
+ /// Set the gain of the front-end.
+ pub fn set_gain(&mut self, gain: Gain) {
+ if (gain as u8 & 0b01) != 0 {
+ self.a0.set_high().unwrap();
+ } else {
+ self.a0.set_low().unwrap();
+ }
+
+ if (gain as u8 & 0b10) != 0 {
+ self.a1.set_high().unwrap()
+ } else {
+ self.a1.set_low().unwrap();
+ }
+ }
+
+ /// Get the programmed gain of the analog front-end.
+ pub fn get_gain(&self) -> Gain {
+ let mut code: u8 = 0;
+ if self.a0.is_set_high().unwrap() {
+ code |= 0b1;
+ }
+ if self.a1.is_set_high().unwrap() {
+ code |= 0b10;
+ }
+
+ // NOTE(unwrap): All possibilities covered.
+ Gain::try_from(code).unwrap()
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +
//! STM32 Temperature Sensor Driver
+//!
+//! # Description
+//! This file provides an API for measuring the internal STM32 temperature sensor. This temperature
+//! sensor measures the silicon junction temperature (Tj) and is connected via an internal ADC.
+use stm32h7xx_hal::{
+ self as hal,
+ signature::{TS_CAL_110, TS_CAL_30},
+};
+
+use super::shared_adc::{AdcChannel, AdcError};
+
+/// Helper utility to convert raw codes into temperature measurements.
+struct Calibration {
+ slope: f32,
+ offset: f32,
+}
+
+impl Calibration {
+ /// Construct the calibration utility.
+ pub fn new() -> Self {
+ let ts_cal2 = TS_CAL_110::read();
+ let ts_cal1 = TS_CAL_30::read();
+ let slope = (110. - 30.) / (ts_cal2 as f32 - ts_cal1 as f32);
+ let offset = 30. - slope * ts_cal1 as f32;
+ Self { slope, offset }
+ }
+
+ /// Convert a raw ADC sample to a temperature in degrees Celsius.
+ pub fn sample_to_temperature(&self, sample: u32) -> f32 {
+ // We use a 2.048V reference, but calibration data was taken at 3.3V.
+ let sample_3v3 = sample as f32 * 2.048 / 3.3;
+
+ self.slope * sample_3v3 + self.offset
+ }
+}
+
+/// A driver to access the CPU temeprature sensor.
+pub struct CpuTempSensor {
+ sensor: AdcChannel<'static, hal::stm32::ADC3, hal::adc::Temperature>,
+ calibration: Calibration,
+}
+
+impl CpuTempSensor {
+ /// Construct the temperature sensor.
+ ///
+ /// # Args
+ /// * `sensor` - The ADC channel of the integrated temperature sensor.
+ pub fn new(
+ sensor: AdcChannel<'static, hal::stm32::ADC3, hal::adc::Temperature>,
+ ) -> Self {
+ Self {
+ sensor,
+ calibration: Calibration::new(),
+ }
+ }
+
+ /// Get the temperature of the CPU in degrees Celsius.
+ pub fn get_temperature(&mut self) -> Result<f32, AdcError> {
+ self.sensor
+ .read_raw()
+ .map(|raw| self.calibration.sample_to_temperature(raw))
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +
//! Stabilizer DAC management interface
+//!
+//! # Design
+//!
+//! Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC
+//! accepts a 16-bit output code.
+//!
+//! In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using
+//! a timer compare channel, DMA stream, and the DAC SPI interface.
+//!
+//! The timer comparison channel is configured to generate a DMA request whenever the comparison
+//! occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By
+//! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update
+//! the DAC without requiring the CPU.
+//!
+//! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC
+//! output batch is always exactly 3 batches after the ADC batch that generated it.
+//!
+//! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any
+//! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA
+//! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the
+//! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes,
+//! software then has exactly one batch duration to fill the next buffer before its
+//! transfer begins. If software does not meet this deadline, old data will be repeatedly generated
+//! on the output and output will be shifted by one batch.
+//!
+//! ## Multiple Samples to Single DAC Codes
+//!
+//! For some applications, it may be desirable to generate a single DAC code from multiple ADC
+//! samples. In order to maintain timing characteristics between ADC samples and DAC code outputs,
+//! applications are required to generate one DAC code for each ADC sample. To accomodate mapping
+//! multiple inputs to a single output, the output code can be repeated a number of times in the
+//! output buffer corresponding with the number of input samples that were used to generate it.
+//!
+//!
+//! # Note
+//!
+//! There is a very small amount of latency between updating the two DACs due to bus matrix
+//! priority. As such, one of the DACs will be updated marginally earlier before the other because
+//! the DMA requests are generated simultaneously. This can be avoided by providing a known offset
+//! to other DMA requests, which can be completed by setting e.g. DAC0's comparison to a
+//! counter value of 2 and DAC1's comparison to a counter value of 3. This will have the effect of
+//! generating the DAC updates with a known latency of 1 timer tick to each other and prevent the
+//! DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the
+//! DMA channels to arbitrate which transfer occurs first.
+//!
+//!
+//! # Limitations
+//!
+//! While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check
+//! for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is
+//! served promptly after the transfer completes.
+use stm32h7xx_hal as hal;
+
+use mutex_trait::Mutex;
+
+use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
+use super::timers;
+
+use core::convert::TryFrom;
+
+use hal::{
+ dma::{
+ dma::{DMAReq, DmaConfig},
+ traits::TargetAddress,
+ DMAError, MemoryToPeripheral, Transfer,
+ },
+ spi::{HalDisabledSpi, HalEnabledSpi, HalSpi},
+};
+
+// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
+// each transfer in a ping-pong buffer configuration (one is being prepared while the other is being
+// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
+// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
+#[link_section = ".axisram.buffers"]
+static mut DAC_BUF: [[SampleBuffer; 2]; 2] =
+ [[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
+
+/// Custom type for referencing DAC output codes.
+/// The internal integer is the raw code written to the DAC output register.
+#[derive(Copy, Clone)]
+pub struct DacCode(pub u16);
+impl DacCode {
+ // The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
+ // V with 16-bit resolution. The anti-aliasing filter has an additional gain of 2.5.
+ pub const FULL_SCALE: f32 = 4.096 * 2.5;
+ pub const VOLT_PER_LSB: f32 = -Self::FULL_SCALE / i16::MIN as f32;
+ pub const LSB_PER_VOLT: f32 = 1. / Self::VOLT_PER_LSB;
+}
+
+impl TryFrom<f32> for DacCode {
+ type Error = ();
+
+ fn try_from(voltage: f32) -> Result<DacCode, ()> {
+ let code = voltage * Self::LSB_PER_VOLT;
+ if !(i16::MIN as f32..=i16::MAX as f32).contains(&code) {
+ Err(())
+ } else {
+ Ok(DacCode::from(code as i16))
+ }
+ }
+}
+
+impl From<DacCode> for f32 {
+ fn from(code: DacCode) -> f32 {
+ i16::from(code) as f32 * DacCode::VOLT_PER_LSB
+ }
+}
+
+impl From<DacCode> for i16 {
+ fn from(code: DacCode) -> i16 {
+ (code.0 as i16).wrapping_sub(i16::MIN)
+ }
+}
+
+impl From<i16> for DacCode {
+ /// Encode signed 16-bit values into DAC offset binary for a bipolar output configuration.
+ fn from(value: i16) -> Self {
+ Self(value.wrapping_add(i16::MIN) as u16)
+ }
+}
+
+impl From<u16> for DacCode {
+ /// Create a dac code from the provided DAC output code.
+ fn from(value: u16) -> Self {
+ Self(value)
+ }
+}
+
+macro_rules! dac_output {
+ ($name:ident, $index:literal, $data_stream:ident,
+ $spi:ident, $trigger_channel:ident, $dma_req:ident) => {
+ /// $spi is used as a type for indicating a DMA transfer into the SPI TX FIFO
+ struct $spi {
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ _channel: timers::tim2::$trigger_channel,
+ }
+
+ impl $spi {
+ pub fn new(
+ _channel: timers::tim2::$trigger_channel,
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
+ ) -> Self {
+ Self { spi, _channel }
+ }
+
+ /// Start the SPI and begin operating in a DMA-driven transfer mode.
+ pub fn start_dma(&mut self) {
+ // Allow the SPI FIFOs to operate using only DMA data channels.
+ self.spi.enable_dma_tx();
+
+ // Enable SPI and start it in infinite transaction mode.
+ self.spi.inner().cr1.modify(|_, w| w.spe().set_bit());
+ self.spi.inner().cr1.modify(|_, w| w.cstart().started());
+ }
+ }
+
+ // Note(unsafe): This is safe because the DMA request line is logically owned by this module.
+ // Additionally, the SPI is owned by this structure and is known to be configured for u16 word
+ // sizes.
+ unsafe impl TargetAddress<MemoryToPeripheral> for $spi {
+ /// SPI is configured to operate using 16-bit transfer words.
+ type MemSize = u16;
+
+ /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
+
+ /// Whenever the DMA request occurs, it should write into SPI's TX FIFO.
+ fn address(&self) -> usize {
+ &self.spi.inner().txdr as *const _ as usize
+ }
+ }
+
+ /// Represents data associated with DAC.
+ pub struct $name {
+ // Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
+ transfer: Transfer<
+ hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ $spi,
+ MemoryToPeripheral,
+ &'static mut [u16],
+ hal::dma::DBTransfer,
+ >,
+ }
+
+ impl $name {
+ /// Construct the DAC output channel.
+ ///
+ /// # Args
+ /// * `spi` - The SPI interface used to communicate with the ADC.
+ /// * `stream` - The DMA stream used to write DAC codes over SPI.
+ /// * `trigger_channel` - The sampling timer output compare channel for update triggers.
+ pub fn new(
+ spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
+ stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
+ trigger_channel: timers::tim2::$trigger_channel,
+ batch_size: usize,
+ ) -> Self {
+ // Generate DMA events when an output compare of the timer hitting zero (timer roll over)
+ // occurs.
+ trigger_channel.listen_dma();
+ trigger_channel.to_output_compare(4 + $index);
+
+ // The stream constantly writes to the TX FIFO to write new update codes.
+ let trigger_config = DmaConfig::default()
+ .memory_increment(true)
+ .double_buffer(true)
+ .peripheral_increment(false);
+
+ // Listen for any potential SPI error signals, which may indicate that we are not generating
+ // update codes.
+ let mut spi = spi.disable();
+ spi.listen(hal::spi::Event::Error);
+
+ // AXISRAM is uninitialized. As such, we manually initialize it for a 0V DAC output
+ // here before starting the transfer .
+ // Note(unsafe): We currently own all DAC_BUF[index] buffers and are not using them
+ // elsewhere, so it is safe to access them here.
+ for buf in unsafe { DAC_BUF[$index].iter_mut() } {
+ for byte in buf.iter_mut() {
+ *byte = DacCode::try_from(0.0f32).unwrap().0;
+ }
+ }
+
+ // Construct the trigger stream to write from memory to the peripheral.
+ let transfer: Transfer<_, _, MemoryToPeripheral, _, _> =
+ Transfer::init(
+ stream,
+ $spi::new(trigger_channel, spi),
+ // Note(unsafe): This buffer is only used once and provided for the DMA transfer.
+ unsafe { &mut DAC_BUF[$index][0][..batch_size] },
+ // Note(unsafe): This buffer is only used once and provided for the DMA transfer.
+ unsafe { Some(&mut DAC_BUF[$index][1][..batch_size]) },
+ trigger_config,
+ );
+
+ Self { transfer }
+ }
+
+ pub fn start(&mut self) {
+ self.transfer.start(|spi| spi.start_dma());
+ }
+
+ /// Wait for the transfer of the currently active buffer to complete,
+ /// then call a function on the now inactive buffer and acknowledge the
+ /// transfer complete flag.
+ ///
+ /// NOTE(unsafe): Memory safety and access ordering is not guaranteed
+ /// (see the HAL DMA docs).
+ pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
+ where
+ F: FnOnce(&mut &'static mut [u16]) -> R,
+ {
+ unsafe {
+ self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
+ }
+ }
+ }
+
+ // This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
+ // to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
+ impl Mutex for $name {
+ type Data = &'static mut [u16];
+ fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
+ self.with_buffer(f).unwrap()
+ }
+ }
+ };
+}
+
+dac_output!(Dac0Output, 0, Stream6, SPI4, Channel3, Tim2Ch3);
+dac_output!(Dac1Output, 1, Stream7, SPI5, Channel4, Tim2Ch4);
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +
//! Basic blocking delay
+//!
+//! This module provides a basic asm-based blocking delay.
+//!
+//! # Note
+//! This implementation takes into account the Cortex-M7 CPU pipeline architecture to ensure delays
+//! are at least as long as specified.
+use embedded_hal::blocking::delay::{DelayMs, DelayUs};
+
+/// A basic delay implementation.
+pub struct AsmDelay {
+ frequency_us: u32,
+ frequency_ms: u32,
+}
+
+impl AsmDelay {
+ /// Create a new delay.
+ ///
+ /// # Args
+ /// * `freq` - The CPU core frequency.
+ pub fn new(freq: u32) -> AsmDelay {
+ // Note: Frequencies are scaled by 2 to account for the M7 dual instruction pipeline.
+ AsmDelay {
+ frequency_us: (freq / 1_000_000) * 2,
+ frequency_ms: (freq / 1_000) * 2,
+ }
+ }
+}
+
+impl<U> DelayUs<U> for AsmDelay
+where
+ U: Into<u32>,
+{
+ fn delay_us(&mut self, us: U) {
+ cortex_m::asm::delay(self.frequency_us * us.into())
+ }
+}
+
+impl<U> DelayMs<U> for AsmDelay
+where
+ U: Into<u32>,
+{
+ fn delay_ms(&mut self, ms: U) {
+ cortex_m::asm::delay(self.frequency_ms * ms.into())
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +
use stm32h7xx_hal::time::MegaHertz;
+
+/// The system clock, used in various timer calculations
+pub const SYSCLK: MegaHertz = MegaHertz::MHz(400);
+
+/// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock
+/// may begin. This is used for performing the internal ADC conversion.
+pub const ADC_SETUP_TIME: f32 = 220e-9;
+
+/// The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
+pub const ADC_DAC_SCK_MAX: MegaHertz = MegaHertz::MHz(50);
+
+/// The optimal counting frequency of the hardware timers used for timestamping and sampling.
+pub const TIMER_FREQUENCY: MegaHertz = MegaHertz::MHz(100);
+pub const TIMER_PERIOD: f32 = 1. / (TIMER_FREQUENCY.to_Hz() as f32);
+
+/// The QSPI frequency for communicating with the pounder DDS.
+pub const POUNDER_QSPI_FREQUENCY: MegaHertz = MegaHertz::MHz(50);
+
+/// The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS.
+// Pending Pounder Profile writes are up to 32 bytes (QSPI FIFO depth),
+// with 2 cycles required per byte, coming out to a total of 64 QSPI clock cycles.
+// The QSPI is configured for 50MHz, so this comes out to an offset
+// of 1280 ns. We use 1300 ns to be safe.
+pub const POUNDER_IO_UPDATE_DELAY: f32 = 1_300e-9;
+
+/// The duration to assert IO_Update for the pounder DDS.
+// IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile write. With pounder
+// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 500MHz), this corresponds to
+// 32ns. To accomodate rounding errors, we use 50ns instead.
+pub const POUNDER_IO_UPDATE_DURATION: f32 = 50e-9;
+
+/// The DDS reference clock frequency in MHz.
+pub const DDS_REF_CLK: MegaHertz = MegaHertz::MHz(100);
+
+/// The multiplier used for the DDS reference clock PLL.
+pub const DDS_MULTIPLIER: u8 = 5;
+
+/// The DDS system clock frequency after the internal PLL multiplication.
+#[allow(dead_code)]
+pub const DDS_SYSTEM_CLK: MegaHertz =
+ MegaHertz::MHz(DDS_REF_CLK.to_MHz() * DDS_MULTIPLIER as u32);
+
+/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
+#[allow(dead_code)]
+pub const DDS_SYNC_CLK_DIV: u8 = 4;
+
+/// The maximum ADC/DAC sample processing buffer size.
+pub const MAX_SAMPLE_BUFFER_SIZE: usize = 32;
+
+pub type SampleBuffer = [u16; MAX_SAMPLE_BUFFER_SIZE];
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +
use embedded_hal::blocking::{delay::DelayMs, i2c::WriteRead};
+
+// The EEPROM is a variant without address bits, so the 3 LSB of this word are "dont-cares".
+const I2C_ADDR: u8 = 0x50;
+
+// The MAC address is stored in the last 6 bytes of the 256 byte address space.
+const MAC_POINTER: u8 = 0xFA;
+
+pub fn read_eui48<T>(i2c: &mut T, delay: &mut impl DelayMs<u8>) -> [u8; 6]
+where
+ T: WriteRead,
+{
+ let mut previous_read: Option<[u8; 6]> = None;
+ // On Stabilizer v1.1 and earlier hardware, there is a fault where the I2C bus is not connected
+ // to the CPU until the P12V0A rail enables, which can take many seconds, or may never come up
+ // at all. During these transient turn-on conditions, we may fail the I2C read operation. To
+ // accomodate this, we repeat the I2C read for a set number of attempts with a fixed delay
+ // between them. Then, we wait for the bus to stabilize by waiting until the MAC address
+ // read-out is identical for two consecutive reads.
+ for _ in 0..40 {
+ let mut buffer = [0u8; 6];
+ if i2c
+ .write_read(I2C_ADDR, &[MAC_POINTER], &mut buffer)
+ .is_ok()
+ {
+ if let Some(old_read) = previous_read {
+ if old_read == buffer {
+ return buffer;
+ }
+ }
+
+ previous_read.replace(buffer);
+ } else {
+ // Remove any pending previous read if we failed the last attempt.
+ previous_read.take();
+ }
+
+ delay.delay_ms(100);
+ }
+
+ panic!("Failed to read MAC address");
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +
use stm32h7xx_hal::flash::LockedFlashBank;
+
+pub struct Flash(pub LockedFlashBank);
+
+impl Flash {
+ pub fn range(&self) -> core::ops::Range<u32> {
+ 0..(self.0.len() as u32)
+ }
+}
+
+impl embedded_storage::nor_flash::ErrorType for Flash {
+ type Error =
+ <LockedFlashBank as embedded_storage::nor_flash::ErrorType>::Error;
+}
+
+impl embedded_storage::nor_flash::ReadNorFlash for Flash {
+ const READ_SIZE: usize = LockedFlashBank::READ_SIZE;
+
+ fn read(
+ &mut self,
+ offset: u32,
+ bytes: &mut [u8],
+ ) -> Result<(), Self::Error> {
+ self.0.read(offset, bytes)
+ }
+
+ fn capacity(&self) -> usize {
+ self.0.capacity()
+ }
+}
+
+impl embedded_storage::nor_flash::NorFlash for Flash {
+ const WRITE_SIZE: usize =
+ stm32h7xx_hal::flash::UnlockedFlashBank::WRITE_SIZE;
+ const ERASE_SIZE: usize =
+ stm32h7xx_hal::flash::UnlockedFlashBank::ERASE_SIZE;
+
+ fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
+ let mut bank = self.0.unlocked();
+ bank.erase(from, to)
+ }
+
+ fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
+ let mut bank = self.0.unlocked();
+ bank.write(offset, bytes)
+ }
+}
+
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 +
use crate::hardware::HardwareVersion;
+use serde::Serialize;
+
+mod build_info {
+ include!(concat!(env!("OUT_DIR"), "/built.rs"));
+}
+
+#[derive(Serialize)]
+pub struct ApplicationMetadata {
+ pub firmware_version: &'static str,
+ pub rust_version: &'static str,
+ pub profile: &'static str,
+ pub git_dirty: bool,
+ pub features: &'static str,
+ pub panic_info: &'static str,
+ pub hardware_version: HardwareVersion,
+}
+
+impl ApplicationMetadata {
+ /// Construct the global metadata.
+ ///
+ /// # Note
+ /// This may only be called once.
+ ///
+ /// # Args
+ /// * `hardware_version` - The hardware version detected.
+ ///
+ /// # Returns
+ /// A reference to the global metadata.
+ pub fn new(version: HardwareVersion) -> &'static ApplicationMetadata {
+ cortex_m::singleton!(: ApplicationMetadata = ApplicationMetadata {
+ firmware_version: build_info::GIT_VERSION.unwrap_or("Unspecified"),
+ rust_version: build_info::RUSTC_VERSION,
+ profile: build_info::PROFILE,
+ git_dirty: build_info::GIT_DIRTY.unwrap_or(false),
+ features: build_info::FEATURES_STR,
+ hardware_version: version,
+ panic_info: panic_persist::get_panic_message_utf8().unwrap_or("None"),
+ }).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 +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 +
//! Module for all hardware-specific setup of Stabilizer
+
+pub use embedded_hal;
+pub use stm32h7xx_hal as hal;
+
+pub mod adc;
+pub mod afe;
+pub mod cpu_temp_sensor;
+pub mod dac;
+pub mod delay;
+pub mod design_parameters;
+mod eeprom;
+pub mod flash;
+pub mod input_stamper;
+pub mod metadata;
+pub mod platform;
+pub mod pounder;
+pub mod setup;
+pub mod shared_adc;
+pub mod signal_generator;
+pub mod timers;
+
+// Type alias for the analog front-end (AFE) for ADC0.
+pub type AFE0 = afe::ProgrammableGainAmplifier<
+ hal::gpio::gpiof::PF2<hal::gpio::Output<hal::gpio::PushPull>>,
+ hal::gpio::gpiof::PF5<hal::gpio::Output<hal::gpio::PushPull>>,
+>;
+
+// Type alias for the analog front-end (AFE) for ADC1.
+pub type AFE1 = afe::ProgrammableGainAmplifier<
+ hal::gpio::gpiod::PD14<hal::gpio::Output<hal::gpio::PushPull>>,
+ hal::gpio::gpiod::PD15<hal::gpio::Output<hal::gpio::PushPull>>,
+>;
+
+pub type UsbBus = stm32h7xx_hal::usb_hs::UsbBus<stm32h7xx_hal::usb_hs::USB2>;
+
+// Type alias for the USB device.
+pub type UsbDevice = usb_device::device::UsbDevice<'static, UsbBus>;
+
+// 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>>;
+
+pub type SerialTerminal =
+ serial_settings::Runner<'static, crate::settings::SerialSettingsPlatform>;
+
+pub enum HardwareVersion {
+ Rev1_0,
+ Rev1_1,
+ Rev1_2,
+ Rev1_3,
+ Unknown(u8),
+}
+
+impl From<u8> for HardwareVersion {
+ fn from(bitfield: u8) -> Self {
+ match bitfield {
+ 0b000 => HardwareVersion::Rev1_0,
+ 0b001 => HardwareVersion::Rev1_1,
+ 0b010 => HardwareVersion::Rev1_2,
+ 0b011 => HardwareVersion::Rev1_3,
+ other => HardwareVersion::Unknown(other),
+ }
+ }
+}
+
+impl core::fmt::Display for HardwareVersion {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ HardwareVersion::Rev1_0 => write!(f, "v1.0"),
+ HardwareVersion::Rev1_1 => write!(f, "v1.1"),
+ HardwareVersion::Rev1_2 => write!(f, "v1.2"),
+ HardwareVersion::Rev1_3 => write!(f, "v1.3"),
+ HardwareVersion::Unknown(other) => {
+ write!(f, "Unknown ({:#b})", other)
+ }
+ }
+ }
+}
+
+impl serde::Serialize for HardwareVersion {
+ fn serialize<S: serde::Serializer>(
+ &self,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ use core::fmt::Write;
+
+ let mut version_string: heapless::String<32> = heapless::String::new();
+ write!(&mut version_string, "{}", self).unwrap();
+ serializer.serialize_str(&version_string)
+ }
+}
+
+#[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();
+ }
+
+ panic_persist::report_panic_info(info);
+
+ // 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 +
/// Flag used to indicate that a reboot to DFU is requested.
+const DFU_REBOOT_FLAG: u32 = 0xDEAD_BEEF;
+
+/// Indicate a reboot to DFU is requested.
+pub fn start_dfu_reboot() {
+ extern "C" {
+ static mut _bootflag: u8;
+ }
+
+ unsafe {
+ let start_ptr = &mut _bootflag as *mut u8;
+ core::ptr::write_unaligned(start_ptr.cast::<u32>(), DFU_REBOOT_FLAG);
+ }
+
+ cortex_m::peripheral::SCB::sys_reset();
+}
+
+/// Check if the DFU reboot flag is set, indicating a reboot to DFU is requested.
+pub fn dfu_bootflag() -> bool {
+ // Obtain panic region start and end from linker symbol _panic_dump_start and _panic_dump_end
+ extern "C" {
+ static mut _bootflag: u8;
+ }
+
+ unsafe {
+ let start_ptr = &mut _bootflag as *mut u8;
+ let set = DFU_REBOOT_FLAG
+ == core::ptr::read_unaligned(start_ptr.cast::<u32>());
+
+ // Clear the boot flag after checking it to ensure it doesn't stick between reboots.
+ core::ptr::write_unaligned(start_ptr.cast::<u32>(), 0);
+ set
+ }
+}
+
+/// Execute the DFU bootloader stored in system memory.
+///
+/// # Note
+/// This function must be called before any system configuration is performed, as the DFU
+/// bootloader expects the system in an uninitialized state.
+pub fn execute_system_bootloader() {
+ // This process is largely adapted from
+ // https://community.st.com/t5/stm32-mcus/jump-to-bootloader-from-application-on-stm32h7-devices/ta-p/49510
+ cortex_m::interrupt::disable();
+
+ // Disable the SysTick peripheral.
+ let systick = unsafe { &*cortex_m::peripheral::SYST::PTR };
+ unsafe {
+ systick.csr.write(0);
+ systick.rvr.write(0);
+ systick.cvr.write(0);
+ }
+
+ // Clear NVIC interrupt flags and enables.
+ let nvic = unsafe { &*cortex_m::peripheral::NVIC::PTR };
+ for reg in nvic.icer.iter() {
+ unsafe {
+ reg.write(u32::MAX);
+ }
+ }
+
+ for reg in nvic.icpr.iter() {
+ unsafe {
+ reg.write(u32::MAX);
+ }
+ }
+
+ unsafe { cortex_m::interrupt::enable() };
+
+ // The chip does not provide a means to modify the BOOT pins during
+ // run-time. Jump to the bootloader in system memory instead.
+ unsafe {
+ let system_memory_address: *const u32 = 0x1FF0_9800 as *const u32;
+ log::info!("Jumping to DFU");
+ cortex_m::asm::bootload(system_memory_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 +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 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1090 +1091 +1092 +1093 +1094 +1095 +1096 +1097 +1098 +1099 +1100 +1101 +1102 +1103 +1104 +1105 +1106 +1107 +1108 +1109 +1110 +1111 +1112 +1113 +1114 +1115 +1116 +1117 +1118 +1119 +1120 +1121 +1122 +1123 +1124 +1125 +1126 +1127 +1128 +1129 +1130 +1131 +1132 +1133 +1134 +1135 +1136 +1137 +1138 +1139 +1140 +1141 +1142 +1143 +1144 +1145 +1146 +1147 +1148 +1149 +1150 +1151 +1152 +1153 +1154 +1155 +1156 +
//! Stabilizer hardware configuration
+//!
+//! This file contains all of the hardware-specific configuration of Stabilizer.
+use bit_field::BitField;
+use core::sync::atomic::{self, AtomicBool, Ordering};
+use core::{fmt::Write, ptr, slice};
+use stm32h7xx_hal::{
+ self as hal,
+ ethernet::{self, PHY},
+ gpio::Speed,
+ prelude::*,
+};
+
+use smoltcp_nal::smoltcp;
+
+use super::{
+ adc, afe, cpu_temp_sensor::CpuTempSensor, dac, delay, design_parameters,
+ eeprom, input_stamper::InputStamper, metadata::ApplicationMetadata,
+ platform, pounder, pounder::dds_output::DdsOutput, shared_adc::SharedAdc,
+ timers, DigitalInput0, DigitalInput1, EemDigitalInput0, EemDigitalInput1,
+ EemDigitalOutput0, EemDigitalOutput1, EthernetPhy, HardwareVersion,
+ NetworkStack, SerialTerminal, SystemTimer, Systick, UsbBus, UsbDevice,
+ AFE0, AFE1,
+};
+
+const NUM_TCP_SOCKETS: usize = 4;
+const NUM_UDP_SOCKETS: usize = 1;
+const NUM_SOCKETS: usize = NUM_UDP_SOCKETS + NUM_TCP_SOCKETS;
+
+pub struct NetStorage {
+ pub ip_addrs: [smoltcp::wire::IpCidr; 1],
+
+ // Note: There is an additional socket set item required for the DHCP and DNS sockets
+ // respectively.
+ pub sockets: [smoltcp::iface::SocketStorage<'static>; NUM_SOCKETS + 2],
+ pub tcp_socket_storage: [TcpSocketStorage; NUM_TCP_SOCKETS],
+ pub udp_socket_storage: [UdpSocketStorage; NUM_UDP_SOCKETS],
+ pub dns_storage: [Option<smoltcp::socket::dns::DnsQuery>; 1],
+}
+
+#[derive(Copy, Clone)]
+pub struct UdpSocketStorage {
+ rx_storage: [u8; 1024],
+ tx_storage: [u8; 2048],
+ tx_metadata: [smoltcp::storage::PacketMetadata<
+ smoltcp::socket::udp::UdpMetadata,
+ >; 10],
+ rx_metadata: [smoltcp::storage::PacketMetadata<
+ smoltcp::socket::udp::UdpMetadata,
+ >; 10],
+}
+
+impl UdpSocketStorage {
+ const fn new() -> Self {
+ Self {
+ rx_storage: [0; 1024],
+ tx_storage: [0; 2048],
+ tx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
+ rx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct TcpSocketStorage {
+ rx_storage: [u8; 1024],
+ tx_storage: [u8; 1024],
+}
+
+impl TcpSocketStorage {
+ const fn new() -> Self {
+ Self {
+ rx_storage: [0; 1024],
+ tx_storage: [0; 1024],
+ }
+ }
+}
+
+impl Default for NetStorage {
+ fn default() -> Self {
+ NetStorage {
+ // Placeholder for the real IP address, which is initialized at runtime.
+ ip_addrs: [smoltcp::wire::IpCidr::Ipv6(
+ smoltcp::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX,
+ )],
+ sockets: [smoltcp::iface::SocketStorage::EMPTY; NUM_SOCKETS + 2],
+ tcp_socket_storage: [TcpSocketStorage::new(); NUM_TCP_SOCKETS],
+ udp_socket_storage: [UdpSocketStorage::new(); NUM_UDP_SOCKETS],
+ dns_storage: [None; 1],
+ }
+ }
+}
+
+/// The available networking devices on Stabilizer.
+pub struct NetworkDevices {
+ pub stack: NetworkStack,
+ pub phy: EthernetPhy,
+ pub mac_address: smoltcp::wire::EthernetAddress,
+}
+
+/// The GPIO pins available on the EEM connector, if Pounder is not present.
+pub struct EemGpioDevices {
+ pub lvds4: EemDigitalInput0,
+ pub lvds5: EemDigitalInput1,
+ pub lvds6: EemDigitalOutput0,
+ pub lvds7: EemDigitalOutput1,
+}
+
+/// The available hardware interfaces on Stabilizer.
+pub struct StabilizerDevices {
+ pub systick: Systick,
+ pub temperature_sensor: CpuTempSensor,
+ pub afes: (AFE0, AFE1),
+ pub adcs: (adc::Adc0Input, adc::Adc1Input),
+ pub dacs: (dac::Dac0Output, dac::Dac1Output),
+ pub timestamper: InputStamper,
+ pub adc_dac_timer: timers::SamplingTimer,
+ pub timestamp_timer: timers::TimestampTimer,
+ pub net: NetworkDevices,
+ pub digital_inputs: (DigitalInput0, DigitalInput1),
+ pub eem_gpio: EemGpioDevices,
+ pub usb_serial: SerialTerminal,
+ pub usb: UsbDevice,
+ pub metadata: &'static ApplicationMetadata,
+}
+
+/// 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");
+ }
+
+ // Check for a reboot to DFU before doing any system configuration.
+ if platform::dfu_bootflag() {
+ platform::execute_system_bootloader();
+ }
+
+ let pwr = device.PWR.constrain();
+ let vos = pwr.freeze();
+
+ // Enable SRAM3 for the ethernet descriptor ring.
+ device.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit());
+
+ // Clear reset flags.
+ device.RCC.rsr.write(|w| w.rmvf().set_bit());
+
+ // Select the PLLs for SPI.
+ device
+ .RCC
+ .d2ccip1r
+ .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q());
+
+ device.RCC.d1ccipr.modify(|_, w| w.qspisel().rcc_hclk3());
+
+ device.RCC.d3ccipr.modify(|_, w| w.adcsel().per());
+
+ let rcc = device.RCC.constrain();
+ let mut ccdr = rcc
+ .use_hse(8.MHz())
+ .sysclk(design_parameters::SYSCLK.convert())
+ .hclk(200.MHz())
+ .per_ck(64.MHz()) // fixed frequency HSI, only used for internal ADC. This is not the "peripheral" clock for timers and others.
+ .pll2_p_ck(100.MHz())
+ .pll2_q_ck(100.MHz())
+ .freeze(vos, &device.SYSCFG);
+
+ // Set up USB clocks.
+ ccdr.clocks.hsi48_ck().unwrap();
+ ccdr.peripheral
+ .kernel_usb_clk_mux(stm32h7xx_hal::rcc::rec::UsbClkSel::Hsi48);
+
+ // Before being able to call any code in ITCM, load that code from flash.
+ load_itcm();
+
+ let systick = Systick::new(core.SYST, ccdr.clocks.sysclk().to_Hz());
+
+ // After ITCM loading.
+ core.SCB.enable_icache();
+
+ let mut delay = delay::AsmDelay::new(ccdr.clocks.c_ck().to_Hz());
+
+ let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA);
+ let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB);
+ let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC);
+ let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD);
+ let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE);
+ let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF);
+ let mut gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG);
+
+ let dma_streams =
+ hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
+
+ // Verify that batch period does not exceed RTIC Monotonic timer period.
+ assert!(
+ (batch_size as u32 * sample_ticks) as f32
+ * design_parameters::TIMER_PERIOD
+ * (super::MONOTONIC_FREQUENCY as f32)
+ < 1.
+ );
+
+ // Configure timer 2 to trigger conversions for the ADC
+ let mut sampling_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer2 =
+ device
+ .TIM2
+ .timer(1.kHz(), ccdr.peripheral.TIM2, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer2.pause();
+ timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ let mut sampling_timer = timers::SamplingTimer::new(timer2);
+ sampling_timer.set_period_ticks(sample_ticks - 1);
+
+ // The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
+ // it generates a trigger whenever it is enabled.
+
+ sampling_timer
+ };
+
+ let mut shadow_sampling_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer3 =
+ device
+ .TIM3
+ .timer(1.kHz(), ccdr.peripheral.TIM3, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer3.pause();
+ timer3.reset_counter();
+ timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ let mut shadow_sampling_timer =
+ timers::ShadowSamplingTimer::new(timer3);
+ shadow_sampling_timer.set_period_ticks(sample_ticks as u16 - 1);
+
+ // The shadow sampling timer is a slave-mode timer to the sampling timer. It should
+ // always be in-sync - thus, we configure it to operate in slave mode using "Trigger
+ // mode".
+ // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the
+ // SamplingTimer start now gates the start of the ShadowSamplingTimer.
+ shadow_sampling_timer.set_slave_mode(
+ timers::TriggerSource::Trigger1,
+ timers::SlaveMode::Trigger,
+ );
+
+ shadow_sampling_timer
+ };
+
+ let sampling_timer_channels = sampling_timer.channels();
+ let shadow_sampling_timer_channels = shadow_sampling_timer.channels();
+
+ let mut timestamp_timer = {
+ // The timer frequency is manually adjusted below, so the 1KHz setting here is a
+ // dont-care.
+ let mut timer5 =
+ device
+ .TIM5
+ .timer(1.kHz(), ccdr.peripheral.TIM5, &ccdr.clocks);
+
+ // Configure the timer to count at the designed tick rate. We will manually set the
+ // period below.
+ timer5.pause();
+ timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
+
+ // The timestamp timer runs at the counter cycle period as the sampling timers.
+ // To accomodate this, we manually set the prescaler identical to the sample
+ // timer, but use maximum overflow period.
+ let mut timer = timers::TimestampTimer::new(timer5);
+
+ // TODO: Check hardware synchronization of timestamping and the sampling timers
+ // for phase shift determinism.
+
+ timer.set_period_ticks(u32::MAX);
+
+ timer
+ };
+
+ let timestamp_timer_channels = timestamp_timer.channels();
+
+ // Configure the SPI interfaces to the ADCs and DACs.
+ let adcs = {
+ let adc0 = {
+ let miso = gpiob.pb14.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpiob.pb10.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpiob.pb9.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: design_parameters::ADC_SETUP_TIME,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Receiver);
+
+ let spi: hal::spi::Spi<_, _, u16> = device.SPI2.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI2,
+ &ccdr.clocks,
+ );
+
+ adc::Adc0Input::new(
+ spi,
+ dma_streams.0,
+ dma_streams.1,
+ dma_streams.2,
+ sampling_timer_channels.ch1,
+ shadow_sampling_timer_channels.ch1,
+ batch_size,
+ )
+ };
+
+ let adc1 = {
+ let miso = gpiob.pb4.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpioc.pc10.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpioa.pa15.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: design_parameters::ADC_SETUP_TIME,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Receiver);
+
+ let spi: hal::spi::Spi<_, _, u16> = device.SPI3.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI3,
+ &ccdr.clocks,
+ );
+
+ adc::Adc1Input::new(
+ spi,
+ dma_streams.3,
+ dma_streams.4,
+ dma_streams.5,
+ sampling_timer_channels.ch2,
+ shadow_sampling_timer_channels.ch2,
+ batch_size,
+ )
+ };
+
+ (adc0, adc1)
+ };
+
+ let dacs = {
+ let mut dac_clr_n = gpioe.pe12.into_push_pull_output();
+ dac_clr_n.set_high();
+
+ let dac0_spi = {
+ let miso = gpioe.pe5.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpioe.pe2.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpioe.pe4.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: 0.0,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Transmitter)
+ .swap_mosi_miso();
+
+ device.SPI4.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI4,
+ &ccdr.clocks,
+ )
+ };
+
+ let dac1_spi = {
+ let miso = gpiof.pf8.into_alternate().speed(Speed::VeryHigh);
+ let sck = gpiof.pf7.into_alternate().speed(Speed::VeryHigh);
+ let nss = gpiof.pf6.into_alternate().speed(Speed::VeryHigh);
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ })
+ .hardware_cs(hal::spi::HardwareCS {
+ mode: hal::spi::HardwareCSMode::WordTransaction,
+ assertion_delay: 0.0,
+ polarity: hal::spi::Polarity::IdleHigh,
+ })
+ .communication_mode(hal::spi::CommunicationMode::Transmitter)
+ .swap_mosi_miso();
+
+ device.SPI5.spi(
+ (sck, miso, hal::spi::NoMosi, nss),
+ config,
+ design_parameters::ADC_DAC_SCK_MAX.convert(),
+ ccdr.peripheral.SPI5,
+ &ccdr.clocks,
+ )
+ };
+
+ let dac0 = dac::Dac0Output::new(
+ dac0_spi,
+ dma_streams.6,
+ sampling_timer_channels.ch3,
+ batch_size,
+ );
+ let dac1 = dac::Dac1Output::new(
+ dac1_spi,
+ dma_streams.7,
+ sampling_timer_channels.ch4,
+ batch_size,
+ );
+
+ dac_clr_n.set_low();
+ // dac0_ldac_n
+ gpioe.pe11.into_push_pull_output().set_low();
+ // dac1_ldac_n
+ gpioe.pe15.into_push_pull_output().set_low();
+ dac_clr_n.set_high();
+
+ (dac0, dac1)
+ };
+
+ let afes = {
+ // AFE_PWR_ON on hardware revision v1.3.2
+ gpioe.pe1.into_push_pull_output().set_high();
+
+ let afe0 = {
+ let a0_pin = gpiof.pf2.into_push_pull_output();
+ let a1_pin = gpiof.pf5.into_push_pull_output();
+ afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
+ };
+
+ let afe1 = {
+ let a0_pin = gpiod.pd14.into_push_pull_output();
+ let a1_pin = gpiod.pd15.into_push_pull_output();
+ afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
+ };
+
+ (afe0, afe1)
+ };
+
+ let input_stamper = {
+ let trigger = gpioa.pa3.into_alternate();
+ InputStamper::new(trigger, timestamp_timer_channels.ch4)
+ };
+
+ let digital_inputs = {
+ let di0 = gpiog.pg9.into_floating_input();
+ let di1 = gpioc.pc15.into_floating_input();
+ (di0, di1)
+ };
+
+ let mut eeprom_i2c = {
+ let sda = gpiof.pf0.into_alternate().set_open_drain();
+ let scl = gpiof.pf1.into_alternate().set_open_drain();
+ device.I2C2.i2c(
+ (scl, sda),
+ 100.kHz(),
+ ccdr.peripheral.I2C2,
+ &ccdr.clocks,
+ )
+ };
+
+ let metadata = {
+ // Read the hardware version pins.
+ let hardware_version = {
+ let hwrev0 = gpiog.pg0.into_pull_down_input();
+ let hwrev1 = gpiog.pg1.into_pull_down_input();
+ let hwrev2 = gpiog.pg2.into_pull_down_input();
+ let hwrev3 = gpiog.pg3.into_pull_down_input();
+
+ HardwareVersion::from(
+ *0u8.set_bit(0, hwrev0.is_high())
+ .set_bit(1, hwrev1.is_high())
+ .set_bit(2, hwrev2.is_high())
+ .set_bit(3, hwrev3.is_high()),
+ )
+ };
+
+ ApplicationMetadata::new(hardware_version)
+ };
+
+ let mac_addr = smoltcp::wire::EthernetAddress(eeprom::read_eui48(
+ &mut eeprom_i2c,
+ &mut delay,
+ ));
+ log::info!("EUI48: {}", mac_addr);
+
+ let network_devices = {
+ let ethernet_pins = {
+ // Reset the PHY before configuring pins.
+ let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output();
+ eth_phy_nrst.set_low();
+ delay.delay_us(200u8);
+ eth_phy_nrst.set_high();
+
+ let ref_clk = gpioa.pa1.into_alternate().speed(Speed::VeryHigh);
+ let mdio = gpioa.pa2.into_alternate().speed(Speed::VeryHigh);
+ let mdc = gpioc.pc1.into_alternate().speed(Speed::VeryHigh);
+ let crs_dv = gpioa.pa7.into_alternate().speed(Speed::VeryHigh);
+ let rxd0 = gpioc.pc4.into_alternate().speed(Speed::VeryHigh);
+ let rxd1 = gpioc.pc5.into_alternate().speed(Speed::VeryHigh);
+ let tx_en = gpiob.pb11.into_alternate().speed(Speed::VeryHigh);
+ let txd0 = gpiob.pb12.into_alternate().speed(Speed::VeryHigh);
+ let txd1 = gpiog.pg14.into_alternate().speed(Speed::VeryHigh);
+
+ (ref_clk, mdio, mdc, crs_dv, rxd0, rxd1, tx_en, txd0, txd1)
+ };
+
+ // Configure the ethernet controller
+ let (mut eth_dma, eth_mac) = ethernet::new(
+ device.ETHERNET_MAC,
+ device.ETHERNET_MTL,
+ device.ETHERNET_DMA,
+ ethernet_pins,
+ // Note(unsafe): We only call this function once to take ownership of the
+ // descriptor ring.
+ unsafe { &mut DES_RING },
+ mac_addr,
+ ccdr.peripheral.ETH1MAC,
+ &ccdr.clocks,
+ );
+
+ // Reset and initialize the ethernet phy.
+ let mut lan8742a =
+ ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
+ lan8742a.phy_reset();
+ lan8742a.phy_init();
+
+ unsafe { ethernet::enable_interrupt() };
+
+ // Configure IP address according to DHCP socket availability
+ let ip_addrs: smoltcp::wire::IpAddress = option_env!("STATIC_IP")
+ .unwrap_or("0.0.0.0")
+ .parse()
+ .unwrap();
+
+ let random_seed = {
+ let mut rng =
+ device.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks);
+ let mut data = [0u8; 8];
+ rng.fill(&mut data).unwrap();
+ data
+ };
+
+ // Note(unwrap): The hardware configuration function is only allowed to be called once.
+ // Unwrapping is intended to panic if called again to prevent re-use of global memory.
+ let store =
+ cortex_m::singleton!(: NetStorage = NetStorage::default()).unwrap();
+
+ store.ip_addrs[0] = smoltcp::wire::IpCidr::new(ip_addrs, 24);
+
+ let mut ethernet_config = smoltcp::iface::Config::new(
+ smoltcp::wire::HardwareAddress::Ethernet(mac_addr),
+ );
+ ethernet_config.random_seed = u64::from_be_bytes(random_seed);
+
+ let mut interface = smoltcp::iface::Interface::new(
+ ethernet_config,
+ &mut eth_dma,
+ smoltcp::time::Instant::ZERO,
+ );
+
+ interface
+ .routes_mut()
+ .add_default_ipv4_route(smoltcp::wire::Ipv4Address::UNSPECIFIED)
+ .unwrap();
+
+ interface.update_ip_addrs(|ref mut addrs| {
+ if !ip_addrs.is_unspecified() {
+ addrs
+ .push(smoltcp::wire::IpCidr::new(ip_addrs, 24))
+ .unwrap();
+ }
+ });
+
+ let mut sockets =
+ smoltcp::iface::SocketSet::new(&mut store.sockets[..]);
+ for storage in store.tcp_socket_storage[..].iter_mut() {
+ let tcp_socket = {
+ let rx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
+ &mut storage.rx_storage[..],
+ );
+ let tx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
+ &mut storage.tx_storage[..],
+ );
+
+ smoltcp::socket::tcp::Socket::new(rx_buffer, tx_buffer)
+ };
+
+ sockets.add(tcp_socket);
+ }
+
+ if ip_addrs.is_unspecified() {
+ sockets.add(smoltcp::socket::dhcpv4::Socket::new());
+ }
+
+ sockets.add(smoltcp::socket::dns::Socket::new(
+ &[],
+ &mut store.dns_storage[..],
+ ));
+
+ for storage in store.udp_socket_storage[..].iter_mut() {
+ let udp_socket = {
+ let rx_buffer = smoltcp::socket::udp::PacketBuffer::new(
+ &mut storage.rx_metadata[..],
+ &mut storage.rx_storage[..],
+ );
+ let tx_buffer = smoltcp::socket::udp::PacketBuffer::new(
+ &mut storage.tx_metadata[..],
+ &mut storage.tx_storage[..],
+ );
+
+ smoltcp::socket::udp::Socket::new(rx_buffer, tx_buffer)
+ };
+
+ sockets.add(udp_socket);
+ }
+
+ let mut stack =
+ smoltcp_nal::NetworkStack::new(interface, eth_dma, sockets, clock);
+
+ stack.seed_random_port(&random_seed);
+
+ NetworkDevices {
+ stack,
+ phy: lan8742a,
+ mac_address: mac_addr,
+ }
+ };
+
+ let mut fp_led_0 = gpiod.pd5.into_push_pull_output();
+ let mut fp_led_1 = gpiod.pd6.into_push_pull_output();
+ let mut fp_led_2 = gpiog.pg4.into_push_pull_output();
+ let mut fp_led_3 = gpiod.pd12.into_push_pull_output();
+
+ fp_led_0.set_low();
+ fp_led_1.set_low();
+ fp_led_2.set_low();
+ fp_led_3.set_low();
+
+ let (adc1, adc2, adc3) = {
+ let (mut adc1, mut adc2) = hal::adc::adc12(
+ device.ADC1,
+ device.ADC2,
+ stm32h7xx_hal::time::Hertz::MHz(25),
+ &mut delay,
+ ccdr.peripheral.ADC12,
+ &ccdr.clocks,
+ );
+ let mut adc3 = hal::adc::Adc::adc3(
+ device.ADC3,
+ stm32h7xx_hal::time::Hertz::MHz(25),
+ &mut delay,
+ ccdr.peripheral.ADC3,
+ &ccdr.clocks,
+ );
+
+ adc1.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc1.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc1.calibrate();
+ adc2.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc2.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc2.calibrate();
+ adc3.set_sample_time(hal::adc::AdcSampleTime::T_810);
+ adc3.set_resolution(hal::adc::Resolution::SixteenBit);
+ adc3.calibrate();
+
+ hal::adc::Temperature::new().enable(&adc3);
+
+ let adc1 = adc1.enable();
+ let adc2 = adc2.enable();
+ let adc3 = adc3.enable();
+
+ (
+ // The ADCs must live as global, mutable singletons so that we can hand out references
+ // to the internal ADC. If they were instead to live within e.g. StabilizerDevices,
+ // they would not yet live in 'static memory, which means that we could not hand out
+ // references during initialization, since those references would be invalidated when
+ // we move StabilizerDevices into the late RTIC resources.
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC1> = SharedAdc::new(adc1.slope() as f32, adc1)).unwrap(),
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC2> = SharedAdc::new(adc2.slope() as f32, adc2)).unwrap(),
+ cortex_m::singleton!(: SharedAdc<hal::stm32::ADC3> = SharedAdc::new(adc3.slope() as f32, adc3)).unwrap(),
+ )
+ };
+
+ // Measure the Pounder PGOOD output to detect if pounder is present on Stabilizer.
+ let pounder_pgood = gpiob.pb13.into_pull_down_input();
+ delay.delay_ms(2u8);
+ let pounder = if pounder_pgood.is_high() {
+ log::info!("Found Pounder");
+
+ let i2c1 = {
+ let sda = gpiob.pb7.into_alternate().set_open_drain();
+ let scl = gpiob.pb8.into_alternate().set_open_drain();
+ let i2c1 = device.I2C1.i2c(
+ (scl, sda),
+ 400.kHz(),
+ ccdr.peripheral.I2C1,
+ &ccdr.clocks,
+ );
+
+ shared_bus::new_atomic_check!(hal::i2c::I2c<hal::stm32::I2C1> = i2c1).unwrap()
+ };
+
+ let io_expander =
+ mcp230xx::Mcp230xx::new_default(i2c1.acquire_i2c()).unwrap();
+
+ let temp_sensor =
+ lm75::Lm75::new(i2c1.acquire_i2c(), lm75::Address::default());
+
+ let spi = {
+ let mosi = gpiod.pd7.into_alternate();
+ let miso = gpioa.pa6.into_alternate();
+ let sck = gpiog.pg11.into_alternate();
+
+ let config = hal::spi::Config::new(hal::spi::Mode {
+ polarity: hal::spi::Polarity::IdleHigh,
+ phase: hal::spi::Phase::CaptureOnSecondTransition,
+ });
+
+ // The maximum frequency of this SPI must be limited due to capacitance on the MISO
+ // line causing a long RC decay.
+ device.SPI1.spi(
+ (sck, miso, mosi),
+ config,
+ 5.MHz(),
+ ccdr.peripheral.SPI1,
+ &ccdr.clocks,
+ )
+ };
+
+ let pwr0 = adc1.create_channel(gpiof.pf11.into_analog());
+ let pwr1 = adc2.create_channel(gpiof.pf14.into_analog());
+ let aux_adc0 = adc3.create_channel(gpiof.pf3.into_analog());
+ let aux_adc1 = adc3.create_channel(gpiof.pf4.into_analog());
+
+ let pounder_devices = pounder::PounderDevices::new(
+ temp_sensor,
+ io_expander,
+ spi,
+ pwr0,
+ pwr1,
+ aux_adc0,
+ aux_adc1,
+ )
+ .unwrap();
+
+ let ad9959 = {
+ let qspi_interface = {
+ // Instantiate the QUADSPI pins and peripheral interface.
+ let qspi_pins = {
+ let _ncs =
+ gpioc.pc11.into_alternate::<9>().speed(Speed::VeryHigh);
+
+ let clk = gpiob.pb2.into_alternate().speed(Speed::VeryHigh);
+ let io0 = gpioe.pe7.into_alternate().speed(Speed::VeryHigh);
+ let io1 = gpioe.pe8.into_alternate().speed(Speed::VeryHigh);
+ let io2 = gpioe.pe9.into_alternate().speed(Speed::VeryHigh);
+ let io3 =
+ gpioe.pe10.into_alternate().speed(Speed::VeryHigh);
+
+ (clk, io0, io1, io2, io3)
+ };
+
+ let qspi = device.QUADSPI.bank2(
+ qspi_pins,
+ design_parameters::POUNDER_QSPI_FREQUENCY.convert(),
+ &ccdr.clocks,
+ ccdr.peripheral.QSPI,
+ );
+
+ pounder::QspiInterface::new(qspi).unwrap()
+ };
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ let reset_pin = gpiog.pg6.into_push_pull_output();
+ #[cfg(feature = "pounder_v1_0")]
+ let reset_pin = gpioa.pa0.into_push_pull_output();
+
+ let mut io_update = gpiog.pg7.into_push_pull_output();
+
+ // Delay to allow the pounder DDS reference clock to fully start up. The exact startup
+ // time is not specified, but bench testing indicates it usually comes up within
+ // 200-300uS. We do a larger delay to ensure that it comes up and is stable before
+ // using it.
+ delay.delay_ms(10u32);
+
+ let mut ad9959 = ad9959::Ad9959::new(
+ qspi_interface,
+ reset_pin,
+ &mut io_update,
+ &mut delay,
+ ad9959::Mode::FourBitSerial,
+ design_parameters::DDS_REF_CLK.to_Hz() as f32,
+ design_parameters::DDS_MULTIPLIER,
+ )
+ .unwrap();
+
+ ad9959.self_test().unwrap();
+
+ // Return IO_Update
+ gpiog.pg7 = io_update.into_analog();
+
+ ad9959
+ };
+
+ let dds_output = {
+ let io_update_trigger = {
+ let _io_update =
+ gpiog.pg7.into_alternate::<2>().speed(Speed::VeryHigh);
+
+ // Configure the IO_Update signal for the DDS.
+ let mut hrtimer = pounder::hrtimer::HighResTimerE::new(
+ device.HRTIM_TIME,
+ device.HRTIM_MASTER,
+ device.HRTIM_COMMON,
+ ccdr.clocks,
+ ccdr.peripheral.HRTIM,
+ );
+
+ // IO_Update occurs after a fixed delay from the QSPI write. Note that the timer
+ // is triggered after the QSPI write, which can take approximately 120nS, so
+ // there is additional margin.
+ hrtimer.configure_single_shot(
+ pounder::hrtimer::Channel::Two,
+ design_parameters::POUNDER_IO_UPDATE_DELAY,
+ design_parameters::POUNDER_IO_UPDATE_DURATION,
+ );
+
+ // Ensure that we have enough time for an IO-update every batch.
+ let sample_frequency = {
+ design_parameters::TIMER_FREQUENCY.to_Hz() as f32
+ / sample_ticks as f32
+ };
+
+ let sample_period = 1.0 / sample_frequency;
+ assert!(
+ sample_period * batch_size as f32
+ > design_parameters::POUNDER_IO_UPDATE_DELAY
+ );
+
+ hrtimer
+ };
+
+ let (qspi, config) = ad9959.freeze();
+ DdsOutput::new(qspi, io_update_trigger, config)
+ };
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ let pounder_stamper = {
+ log::info!("Assuming Pounder v1.1 or later");
+ let etr_pin = gpioa.pa0.into_alternate();
+
+ // The frequency in the constructor is dont-care, as we will modify the period + clock
+ // source manually below.
+ let tim8 =
+ device
+ .TIM8
+ .timer(1.kHz(), ccdr.peripheral.TIM8, &ccdr.clocks);
+ let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8);
+
+ // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
+ // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
+ // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
+ // This is less than fCK_INT/3 of the timer as required for oversampling the trigger.
+ timestamp_timer.set_external_clock(timers::Prescaler::Div4);
+ timestamp_timer.start();
+
+ // Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
+ // Scale and wrap before or after the PLL.
+ timestamp_timer.set_period_ticks(u16::MAX);
+ let tim8_channels = timestamp_timer.channels();
+
+ pounder::timestamp::Timestamper::new(
+ timestamp_timer,
+ tim8_channels.ch1,
+ &mut sampling_timer,
+ etr_pin,
+ batch_size,
+ )
+ };
+
+ Some(PounderDevices {
+ pounder: pounder_devices,
+ dds_output,
+
+ #[cfg(not(feature = "pounder_v1_0"))]
+ timestamper: pounder_stamper,
+ })
+ } else {
+ None
+ };
+
+ let eem_gpio = EemGpioDevices {
+ lvds4: gpiod.pd1.into_floating_input(),
+ lvds5: gpiod.pd2.into_floating_input(),
+ lvds6: gpiod.pd3.into_push_pull_output(),
+ lvds7: gpiod.pd4.into_push_pull_output(),
+ };
+
+ let (usb_device, usb_serial) = {
+ let usb_bus = cortex_m::singleton!(: Option<usb_device::bus::UsbBusAllocator<UsbBus>> = None).unwrap();
+ let endpoint_memory =
+ cortex_m::singleton!(: [u32; 1024] = [0; 1024]).unwrap();
+
+ //let usb_id = gpioa.pa10.into_alternate::<8>();
+ let usb_n = gpioa.pa11.into_alternate();
+ let usb_p = gpioa.pa12.into_alternate();
+
+ let usb = stm32h7xx_hal::usb_hs::USB2::new(
+ device.OTG2_HS_GLOBAL,
+ device.OTG2_HS_DEVICE,
+ device.OTG2_HS_PWRCLK,
+ usb_n,
+ usb_p,
+ ccdr.peripheral.USB2OTG,
+ &ccdr.clocks,
+ );
+
+ // Generate a device serial number from the MAC address.
+ let serial_number =
+ cortex_m::singleton!(: Option<heapless::String<17>> = None)
+ .unwrap();
+ {
+ let mut serial_string: heapless::String<17> =
+ heapless::String::new();
+ let octets = mac_addr.0;
+
+ write!(
+ serial_string,
+ "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
+ octets[0],
+ octets[1],
+ octets[2],
+ octets[3],
+ octets[4],
+ octets[5]
+ )
+ .unwrap();
+ serial_number.replace(serial_string);
+ }
+
+ usb_bus.replace(stm32h7xx_hal::usb_hs::UsbBus::new(
+ usb,
+ &mut endpoint_memory[..],
+ ));
+
+ let serial = usbd_serial::SerialPort::new(usb_bus.as_ref().unwrap());
+ let usb_device = usb_device::device::UsbDeviceBuilder::new(
+ usb_bus.as_ref().unwrap(),
+ usb_device::device::UsbVidPid(0x1209, 0x392F),
+ )
+ .strings(&[usb_device::device::StringDescriptors::default()
+ .manufacturer("ARTIQ/Sinara")
+ .product("Stabilizer")
+ .serial_number(serial_number.as_ref().unwrap())])
+ .unwrap()
+ .device_class(usbd_serial::USB_CLASS_CDC)
+ .build();
+
+ (usb_device, serial)
+ };
+
+ let usb_serial = {
+ let (_, flash_bank2) = device.FLASH.split();
+
+ let input_buffer =
+ cortex_m::singleton!(: [u8; 256] = [0u8; 256]).unwrap();
+ let serialize_buffer =
+ cortex_m::singleton!(: [u8; 512] = [0u8; 512]).unwrap();
+
+ let mut storage = super::flash::Flash(flash_bank2.unwrap());
+ let mut settings =
+ crate::settings::Settings::new(network_devices.mac_address);
+ settings.reload(&mut storage);
+
+ serial_settings::Runner::new(
+ crate::settings::SerialSettingsPlatform {
+ interface: serial_settings::BestEffortInterface::new(
+ usb_serial,
+ ),
+ storage,
+ settings,
+ metadata,
+ },
+ input_buffer,
+ serialize_buffer,
+ )
+ .unwrap()
+ };
+
+ let stabilizer = StabilizerDevices {
+ systick,
+ afes,
+ adcs,
+ dacs,
+ temperature_sensor: CpuTempSensor::new(
+ adc3.create_channel(hal::adc::Temperature::new()),
+ ),
+ timestamper: input_stamper,
+ net: network_devices,
+ adc_dac_timer: sampling_timer,
+ timestamp_timer,
+ digital_inputs,
+ eem_gpio,
+ usb: usb_device,
+ usb_serial,
+ metadata,
+ };
+
+ // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap());
+ // info!("Built on {}", build_info::BUILT_TIME_UTC);
+ // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET);
+ log::info!("setup() complete");
+
+ (stabilizer, pounder)
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +
/// Shared Internal ADC Support
+///
+/// # Description
+/// This module provides an abstraction to share ownership of a single ADC peripheral with multiple
+/// ADC channels attached to it.
+///
+/// The design of this module mimics that of [`shared-bus`].
+///
+/// First, the shared ADC is created with the use of a macro, which places the ADC peripheral into
+/// a mutable, static (singleton) location. Then, individual channels are created by passing in the
+/// associated ADC input pin to the [SharedAdc::create_channel()] function to generate an
+/// [AdcChannel]. The [AdcChannel]'s ownership can then be moved to any required drivers.
+///
+/// ## Synchronization
+/// If the multiple priorities utilize the ADC that results in resource pre-emption, pre-emption is
+/// protected against through the use of an atomic bool. Attempting to utilize the ADC from a
+/// higher priority level while it is in use at a lower level will result in a [AdcError::InUse].
+use embedded_hal::adc::{Channel, OneShot};
+use stm32h7xx_hal as hal;
+
+#[derive(Debug, Copy, Clone)]
+pub enum AdcError {
+ /// Indicates that the ADC is already in use
+ InUse,
+}
+
+/// A single channel on an ADC peripheral.
+pub struct AdcChannel<'a, Adc, PIN> {
+ pin: PIN,
+ slope: f32,
+ mutex: &'a spin::Mutex<hal::adc::Adc<Adc, hal::adc::Enabled>>,
+}
+
+impl<'a, Adc, PIN> AdcChannel<'a, Adc, PIN>
+where
+ PIN: Channel<Adc, ID = u8>,
+ hal::adc::Adc<Adc, hal::adc::Enabled>: OneShot<Adc, u32, PIN>,
+ <hal::adc::Adc<Adc, hal::adc::Enabled> as OneShot<Adc, u32, PIN>>::Error:
+ core::fmt::Debug,
+{
+ /// Read the ADC channel and normalize the result.
+ ///
+ /// # Returns
+ /// The normalized ADC measurement as a ratio of full-scale.
+ pub fn read_normalized(&mut self) -> Result<f32, AdcError> {
+ self.read_raw().map(|code| code as f32 / self.slope)
+ }
+
+ /// Read the raw ADC sample for the channel.
+ ///
+ /// # Returns
+ /// The raw ADC code measured on the channel.
+ pub fn read_raw(&mut self) -> Result<u32, AdcError> {
+ let mut adc = self.mutex.try_lock().ok_or(AdcError::InUse)?;
+ Ok(adc.read(&mut self.pin).unwrap())
+ }
+}
+
+/// An ADC peripheral that can provide ownership of individual channels for sharing between
+/// drivers.
+pub struct SharedAdc<Adc> {
+ mutex: spin::Mutex<hal::adc::Adc<Adc, hal::adc::Enabled>>,
+ slope: f32,
+}
+
+impl<Adc> SharedAdc<Adc> {
+ /// Construct a new shared ADC driver.
+ ///
+ /// # Args
+ /// * `slope` - The slope of the ADC conversion transfer function.
+ /// * `adc` - The ADC peripheral to share.
+ pub fn new(slope: f32, adc: hal::adc::Adc<Adc, hal::adc::Enabled>) -> Self {
+ Self {
+ slope,
+ mutex: spin::Mutex::new(adc),
+ }
+ }
+
+ /// Allocate an ADC channel for usage.
+ ///
+ /// # Args
+ /// * `pin` - The ADC input associated with the desired ADC channel. Often, this is a GPIO pin.
+ ///
+ /// # Returns
+ /// An instantiated [AdcChannel] whose ownership can be transferred to other drivers.
+ pub fn create_channel<PIN: Channel<Adc, ID = u8>>(
+ &self,
+ pin: PIN,
+ ) -> AdcChannel<'_, Adc, PIN> {
+ AdcChannel {
+ pin,
+ slope: self.slope,
+ mutex: &self.mutex,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +
use miniconf::Tree;
+use rand_core::{RngCore, SeedableRng};
+use rand_xorshift::XorShiftRng;
+use serde::{Deserialize, Serialize};
+
+/// Types of signals that can be generated.
+#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
+pub enum Signal {
+ Cosine,
+ Square,
+ Triangle,
+ WhiteNoise,
+}
+
+/// Basic configuration for a generated signal.
+///
+/// # Miniconf Tree
+/// `{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}`
+///
+/// Where `<signal>` may be any of [Signal] variants, `frequency` specifies the signal frequency
+/// in Hertz, `symmetry` specifies the normalized signal symmetry which ranges from 0 - 1.0, and
+/// `amplitude` specifies the signal amplitude in Volts.
+#[derive(Copy, Clone, Debug, Tree)]
+pub struct BasicConfig {
+ /// The signal type that should be generated. See [Signal] variants.
+ pub signal: Signal,
+
+ /// The frequency of the generated signal in Hertz.
+ pub frequency: f32,
+
+ /// The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal.
+ /// At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this
+ /// symmetry is the duty cycle.
+ pub symmetry: f32,
+
+ /// The amplitude of the output signal in volts.
+ pub amplitude: f32,
+
+ /// The phase of the output signal in turns.
+ pub phase: f32,
+}
+
+impl Default for BasicConfig {
+ fn default() -> Self {
+ Self {
+ frequency: 1.0e3,
+ symmetry: 0.5,
+ signal: Signal::Cosine,
+ amplitude: 0.0,
+ phase: 0.0,
+ }
+ }
+}
+
+/// Represents the errors that can occur when attempting to configure the signal generator.
+#[derive(Copy, Clone, Debug)]
+pub enum Error {
+ /// The provided amplitude is out-of-range.
+ InvalidAmplitude,
+ /// The provided symmetry is out of range.
+ InvalidSymmetry,
+ /// The provided frequency is out of range.
+ InvalidFrequency,
+}
+
+impl BasicConfig {
+ /// Convert configuration into signal generator values.
+ ///
+ /// # Args
+ /// * `sample_period` - The time in seconds between samples.
+ /// * `full_scale` - The full scale output voltage.
+ pub fn try_into_config(
+ self,
+ sample_period: f32,
+ full_scale: f32,
+ ) -> Result<Config, Error> {
+ let symmetry_complement = 1.0 - self.symmetry;
+ // Validate symmetry
+ if self.symmetry < 0.0 || symmetry_complement < 0.0 {
+ return Err(Error::InvalidSymmetry);
+ }
+
+ const NYQUIST: f32 = (1u32 << 31) as _;
+ let ftw = self.frequency * sample_period * NYQUIST;
+
+ // Validate base frequency tuning word to be below Nyquist.
+ if ftw < 0.0 || 2.0 * ftw > NYQUIST {
+ return Err(Error::InvalidFrequency);
+ }
+
+ // Calculate the frequency tuning words.
+ // Clip both frequency tuning words to within Nyquist before rounding.
+ let phase_increment = [
+ if self.symmetry * NYQUIST > ftw {
+ ftw / self.symmetry
+ } else {
+ NYQUIST
+ } as i32,
+ if symmetry_complement * NYQUIST > ftw {
+ ftw / symmetry_complement
+ } else {
+ NYQUIST
+ } as i32,
+ ];
+
+ let amplitude = self.amplitude * (i16::MIN as f32 / -full_scale);
+ if !(i16::MIN as f32..=i16::MAX as f32).contains(&litude) {
+ return Err(Error::InvalidAmplitude);
+ }
+
+ let phase = self.phase * (1u64 << 32) as f32;
+
+ Ok(Config {
+ amplitude: amplitude as i16,
+ signal: self.signal,
+ phase_increment,
+ phase_offset: phase as i32,
+ })
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct Config {
+ /// The type of signal being generated
+ pub signal: Signal,
+
+ /// The full-scale output code of the signal
+ pub amplitude: i16,
+
+ /// The frequency tuning word of the signal. Phase is incremented by this amount
+ pub phase_increment: [i32; 2],
+
+ /// The phase offset
+ pub phase_offset: i32,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ signal: Signal::Cosine,
+ amplitude: 0,
+ phase_increment: [0, 0],
+ phase_offset: 0,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct SignalGenerator {
+ phase_accumulator: i32,
+ config: Config,
+ rng: XorShiftRng,
+}
+
+impl SignalGenerator {
+ /// Construct a new signal generator with some specific config.
+ ///
+ /// # Args
+ /// * `config` - The config to use for generating signals.
+ ///
+ /// # Returns
+ /// The generator
+ pub fn new(config: Config) -> Self {
+ Self {
+ config,
+ phase_accumulator: 0,
+ rng: XorShiftRng::from_seed([0; 16]), // zeros will initialize with XorShiftRng internal seed
+ }
+ }
+
+ /// Update waveform generation settings.
+ pub fn update_waveform(&mut self, new_config: Config) {
+ self.config = new_config;
+ }
+
+ /// Clear the phase accumulator.
+ pub fn clear_phase_accumulator(&mut self) {
+ self.phase_accumulator = 0;
+ }
+}
+
+impl core::iter::Iterator for SignalGenerator {
+ type Item = i16;
+
+ /// Get the next value in the generator sequence.
+ fn next(&mut self) -> Option<i16> {
+ let phase = self
+ .phase_accumulator
+ .wrapping_add(self.config.phase_offset);
+ let sign = phase.is_negative();
+ self.phase_accumulator = self
+ .phase_accumulator
+ .wrapping_add(self.config.phase_increment[sign as usize]);
+
+ let scale = match self.config.signal {
+ Signal::Cosine => idsp::cossin(phase).0 >> 16,
+ Signal::Square => {
+ if sign {
+ i16::MIN as i32
+ } else {
+ -(i16::MIN as i32)
+ }
+ }
+ Signal::Triangle => i16::MIN as i32 + (phase >> 15).abs(),
+ Signal::WhiteNoise => self.rng.next_u32() as i32 >> 16,
+ };
+
+ // Calculate the final output result as an i16.
+ Some(((self.config.amplitude as i32 * scale) >> 15) as _)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +
//! The sampling timer is used for managing ADC sampling and external reference timestamping.
+use super::hal;
+use num_enum::TryFromPrimitive;
+
+use hal::stm32::{
+ // TIM1 and TIM8 have identical registers.
+ tim1 as __tim8,
+ tim2 as __tim2,
+ // TIM2 and TIM5 have identical registers.
+ tim2 as __tim5,
+ tim3 as __tim3,
+};
+
+/// The event that should generate an external trigger from the peripheral.
+#[allow(dead_code)]
+pub enum TriggerGenerator {
+ Reset = 0b000,
+ Enable = 0b001,
+ Update = 0b010,
+ ComparePulse = 0b011,
+ Ch1Compare = 0b100,
+ Ch2Compare = 0b101,
+ Ch3Compare = 0b110,
+ Ch4Compare = 0b111,
+}
+
+/// Selects the trigger source for the timer peripheral.
+#[allow(dead_code)]
+pub enum TriggerSource {
+ Trigger0 = 0,
+ Trigger1 = 0b01,
+ Trigger2 = 0b10,
+ Trigger3 = 0b11,
+}
+
+/// Prescalers for externally-supplied reference clocks.
+#[allow(dead_code)]
+#[derive(TryFromPrimitive)]
+#[repr(u8)]
+pub enum Prescaler {
+ Div1 = 0b00,
+ Div2 = 0b01,
+ Div4 = 0b10,
+ Div8 = 0b11,
+}
+
+/// Optional slave operation modes of a timer.
+#[allow(dead_code)]
+pub enum SlaveMode {
+ Disabled = 0,
+ Trigger = 0b0110,
+}
+
+/// Optional input capture preconditioning filter configurations.
+#[allow(dead_code)]
+pub enum InputFilter {
+ Div1N1 = 0b0000,
+ Div1N8 = 0b0011,
+}
+
+macro_rules! timer_channels {
+ ($name:ident, $TY:ident, $size:ty) => {
+ paste::paste! {
+
+ /// The timer used for managing ADC sampling.
+ pub struct $name {
+ timer: hal::timer::Timer<hal::stm32::[< $TY >]>,
+ channels: Option<[< $TY:lower >]::Channels>,
+ update_event: Option<[< $TY:lower >]::UpdateEvent>,
+ }
+
+ impl $name {
+ /// Construct the sampling timer.
+ #[allow(dead_code)]
+ pub fn new(mut timer: hal::timer::Timer<hal::stm32::[< $TY>]>) -> Self {
+ timer.pause();
+
+ Self {
+ timer,
+ // Note(unsafe): Once these channels are taken, we guarantee that we do not
+ // modify any of the underlying timer channel registers, as ownership of the
+ // channels is now provided through the associated channel structures. We
+ // additionally guarantee this can only be called once because there is only
+ // one Timer2 and this resource takes ownership of it once instantiated.
+ channels: unsafe { Some([< $TY:lower >]::Channels::new()) },
+ update_event: unsafe { Some([< $TY:lower >]::UpdateEvent::new()) },
+ }
+ }
+
+ /// Get the timer capture/compare channels.
+ #[allow(dead_code)]
+ pub fn channels(&mut self) -> [< $TY:lower >]::Channels {
+ self.channels.take().unwrap()
+ }
+
+ /// Get the timer update event.
+ #[allow(dead_code)]
+ pub fn update_event(&mut self) -> [< $TY:lower >]::UpdateEvent {
+ self.update_event.take().unwrap()
+ }
+
+ /// Get the period of the timer.
+ #[allow(dead_code)]
+ pub fn get_period(&self) -> $size {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.arr.read().arr().bits()
+ }
+
+ /// Manually set the period of the timer.
+ #[allow(dead_code)]
+ pub fn set_period_ticks(&mut self, period: $size) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.arr.write(|w| w.arr().bits(period));
+
+ // Force the new period to take effect immediately.
+ self.timer.apply_freq();
+ }
+
+ /// Clock the timer from an external source.
+ ///
+ /// # Note:
+ /// * Currently, only an external source applied to ETR is supported.
+ ///
+ /// # Args
+ /// * `prescaler` - The prescaler to use for the external source.
+ #[allow(dead_code)]
+ pub fn set_external_clock(&mut self, prescaler: Prescaler) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ regs.smcr.modify(|_, w| w.etps().bits(prescaler as u8).ece().set_bit());
+
+ // Clear any other prescaler configuration.
+ regs.psc.write(|w| w.psc().bits(0));
+ }
+
+ /// Start the timer.
+ #[allow(dead_code)]
+ pub fn start(&mut self) {
+ // Force a refresh of the frequency settings.
+ self.timer.apply_freq();
+ self.timer.reset_counter();
+
+ self.timer.resume();
+ }
+
+ /// Configure the timer peripheral to generate a trigger based on the provided
+ /// source.
+ #[allow(dead_code)]
+ pub fn generate_trigger(&mut self, source: TriggerGenerator) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerGenerator enumeration is specified such that this is
+ // always in range.
+ regs.cr2.modify(|_, w| w.mms().bits(source as u8));
+
+ }
+
+ /// Select a trigger source for the timer peripheral.
+ #[allow(dead_code)]
+ pub fn set_trigger_source(&mut self, source: TriggerSource) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerSource enumeration is specified such that this is
+ // always in range.
+ regs.smcr.modify(|_, w| unsafe { w.ts().bits(source as u8) } );
+ }
+
+ #[allow(dead_code)]
+ pub fn set_slave_mode(&mut self, source: TriggerSource, mode: SlaveMode) {
+ let regs = unsafe { &*hal::stm32::$TY::ptr() };
+ // Note(unsafe) The TriggerSource and SlaveMode enumerations are specified such
+ // that they are always in range.
+ regs.smcr.modify(|_, w| unsafe { w.sms().bits(mode as u8).ts().bits(source as u8) } );
+ }
+ }
+
+ pub mod [< $TY:lower >] {
+ use stm32h7xx_hal as hal;
+ use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq};
+ use hal::stm32::$TY;
+
+ pub struct UpdateEvent {}
+
+ impl UpdateEvent {
+ /// Create a new update event
+ ///
+ /// # Safety
+ /// This is only safe to call once.
+ #[allow(dead_code)]
+ pub unsafe fn new() -> Self {
+ Self {}
+ }
+
+ /// Enable DMA requests upon timer updates.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ // Note(unsafe): We perform only atomic operations on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.ude().set_bit());
+ }
+
+ /// Trigger a DMA request manually
+ #[allow(dead_code)]
+ pub fn trigger(&self) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.egr.write(|w| w.ug().set_bit());
+ }
+ }
+
+ /// The channels representing the timer.
+ pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+ }
+
+ impl Channels {
+ /// Construct a new set of channels.
+ ///
+ /// # Safety
+ /// This is only safe to call once.
+ #[allow(dead_code)]
+ pub unsafe fn new() -> Self {
+ Self {
+ ch1: Channel1::new(),
+ ch2: Channel2::new(),
+ ch3: Channel3::new(),
+ ch4: Channel4::new(),
+ }
+ }
+ }
+
+ timer_channels!(1, $TY, ccmr1, $size);
+ timer_channels!(2, $TY, ccmr1, $size);
+ timer_channels!(3, $TY, ccmr2, $size);
+ timer_channels!(4, $TY, ccmr2, $size);
+ }
+ }
+ };
+
+ ($index:expr, $TY:ty, $ccmrx:expr, $size:ty) => {
+ paste::paste! {
+ pub use super::[< __ $TY:lower >]::[< $ccmrx _input >]::[< CC $index S_A>] as [< CaptureSource $index >];
+
+ /// A capture/compare channel of the timer.
+ pub struct [< Channel $index >] {}
+
+ /// A capture channel of the timer.
+ pub struct [< Channel $index InputCapture>] {}
+
+ impl [< Channel $index >] {
+ /// Construct a new timer channel.
+ ///
+ /// Note(unsafe): This function must only be called once. Once constructed, the
+ /// constructee guarantees to never modify the timer channel.
+ #[allow(dead_code)]
+ unsafe fn new() -> Self {
+ Self {}
+ }
+
+ /// Allow the channel to generate DMA requests.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit());
+ }
+
+ /// Operate the channel as an output-compare.
+ ///
+ /// # Args
+ /// * `value` - The value to compare the sampling timer's counter against.
+ #[allow(dead_code)]
+ pub fn to_output_compare(&self, value: $size) {
+ let regs = unsafe { &*<$TY>::ptr() };
+ let arr = regs.arr.read().bits() as $size;
+ assert!(value <= arr);
+ regs.ccr[$index - 1].write(|w| w.ccr().bits(value));
+ regs.[< $ccmrx _output >]()
+ .modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) });
+ }
+
+ /// Operate the channel in input-capture mode.
+ ///
+ /// # Args
+ /// * `input` - The input source for the input capture event.
+ #[allow(dead_code)]
+ pub fn into_input_capture(self, input: [< CaptureSource $index >]) -> [< Channel $index InputCapture >]{
+ let regs = unsafe { &*<$TY>::ptr() };
+
+ regs.[< $ccmrx _input >]().modify(|_, w| w.[< cc $index s>]().variant(input));
+
+ [< Channel $index InputCapture >] {}
+ }
+ }
+
+ impl [< Channel $index InputCapture >] {
+ /// Get the latest capture from the channel.
+ #[allow(dead_code)]
+ pub fn latest_capture(&mut self) -> Result<Option<$size>, Option<$size>> {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+
+ if regs.sr.read().[< cc $index if >]().bit_is_set() {
+ // Read the capture value. Reading the captured value clears the flag in the
+ // status register automatically.
+ let result = regs.ccr[$index - 1].read().ccr().bits();
+
+ // Read SR again to check for a potential over-capture. Return an error in
+ // that case.
+ let sr = regs.sr.read();
+ if sr.[< cc $index of >]().bit_is_set() {
+ // NOTE(unsafe) write-back is safe
+ regs.sr.write(|w| unsafe { w.bits(sr.bits()) }.[< cc $index of >]().clear_bit());
+ Err(Some(result))
+ } else {
+ Ok(Some(result))
+ }
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Allow the channel to generate DMA requests.
+ #[allow(dead_code)]
+ pub fn listen_dma(&self) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit());
+ }
+
+ /// Enable the input capture to begin capturing timer values.
+ #[allow(dead_code)]
+ pub fn enable(&mut self) {
+ // Read the latest input capture to clear any pending data in the register.
+ let _ = self.latest_capture();
+
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.ccer.modify(|_, w| w.[< cc $index e >]().set_bit());
+ }
+
+ /// Check if an over-capture event has occurred.
+ #[allow(dead_code)]
+ pub fn check_overcapture(&self) -> bool {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.sr.read().[< cc $index of >]().bit_is_set()
+ }
+
+ /// Configure the input capture input pre-filter.
+ ///
+ /// # Args
+ /// * `filter` - The desired input filter stage configuration. Defaults to disabled.
+ #[allow(dead_code)]
+ pub fn configure_filter(&mut self, filter: super::InputFilter) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
+ }
+
+ /// Configure the input capture prescaler.
+ ///
+ /// # Args
+ /// * `psc` - Prescaler exponent.
+ #[allow(dead_code)]
+ pub fn configure_prescaler(&mut self, prescaler: super::Prescaler) {
+ // Note(unsafe): This channel owns all access to the specific timer channel.
+ // Only atomic operations on completed on the timer registers.
+ let regs = unsafe { &*<$TY>::ptr() };
+ // Note(unsafe): Enum values are all valid.
+ #[allow(unused_unsafe)]
+ regs.[< $ccmrx _input >]().modify(|_, w| unsafe {
+ w.[< ic $index psc >]().bits(prescaler as u8)});
+ }
+ }
+
+ // Note(unsafe): This manually implements DMA support for input-capture channels. This
+ // is safe as it is only completed once per channel and each DMA request is allocated to
+ // each channel as the owner.
+ unsafe impl TargetAddress<PeripheralToMemory> for [< Channel $index InputCapture >] {
+ type MemSize = $size;
+
+ const REQUEST_LINE: Option<u8> = Some(DMAReq::[< $TY:camel Ch $index >]as u8);
+
+ fn address(&self) -> usize {
+ let regs = unsafe { &*<$TY>::ptr() };
+ ®s.ccr[$index - 1] as *const _ as usize
+ }
+ }
+ }
+ };
+}
+
+timer_channels!(SamplingTimer, TIM2, u32);
+timer_channels!(ShadowSamplingTimer, TIM3, u16);
+
+timer_channels!(TimestampTimer, TIM5, u32);
+timer_channels!(PounderTimestampTimer, TIM8, u16);
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +
//! Stabilizer data stream capabilities
+//!
+//! # Design
+//! Data streamining utilizes UDP packets to send live data streams at high throughput.
+//! Packets are always sent in a best-effort fashion, and data may be dropped.
+//!
+//! Stabilizer organizes livestreamed data into batches within a "Frame" that will be sent as a UDP
+//! packet. Each frame consits of a header followed by sequential batch serializations. The packet
+//! header is constant for all streaming capabilities, but the serialization format after the header
+//! is application-defined.
+//!
+//! ## Frame Header
+//! The header consists of the following, all in little-endian.
+//!
+//! * **Magic word 0x057B** (u16): a constant to identify Stabilizer streaming data.
+//! * **Format Code** (u8): a unique ID that indicates the serialization format of each batch of data
+//! in the frame. Refer to [StreamFormat] for further information.
+//! * **Batch Count** (u8): the number of batches of data.
+//! * **Sequence Number** (u32): an the sequence number of the first batch in the frame.
+//! This can be used to determine if and how many stream batches are lost.
+//!
+//! # Example
+//! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception
+//! of livestreamed data.
+use core::mem::MaybeUninit;
+use heapless::{
+ pool::{Box, Init, Pool, Uninit},
+ spsc::{Consumer, Producer, Queue},
+};
+use num_enum::IntoPrimitive;
+use serde::{Deserialize, Serialize};
+use smoltcp_nal::embedded_nal::{IpAddr, Ipv4Addr, SocketAddr, UdpClientStack};
+
+use super::NetworkReference;
+
+// Magic first bytes indicating a UDP frame of straming data
+const MAGIC: u16 = 0x057B;
+
+// The size of the header, calculated in words.
+// The header has a 16-bit magic word, an 8-bit format, 8-bit batch-size, and 32-bit sequence
+// number, which corresponds to 8 bytes.
+const HEADER_SIZE: usize = 8;
+
+// The number of frames that can be buffered.
+const FRAME_COUNT: usize = 4;
+
+// The size of each frame in bytes.
+// Ensure the resulting ethernet frame is within the MTU:
+// 1500 MTU - 40 IP6 header - 8 UDP header
+const FRAME_SIZE: usize = 1500 - 40 - 8;
+
+// The size of the frame queue must be at least as large as the number of frame buffers. Every
+// allocated frame buffer should fit in the queue.
+const FRAME_QUEUE_SIZE: usize = FRAME_COUNT * 2;
+
+// Static storage used for a heapless::Pool of frame buffers.
+static mut FRAME_DATA: [u8; core::mem::size_of::<u8>()
+ * FRAME_SIZE
+ * FRAME_COUNT] = [0; core::mem::size_of::<u8>() * FRAME_SIZE * FRAME_COUNT];
+
+type Frame = [MaybeUninit<u8>; FRAME_SIZE];
+
+/// Represents the destination for the UDP stream to send data to.
+///
+/// # Miniconf
+/// `{"ip": <addr>, "port": <port>}`
+///
+/// * `<addr>` is an array of 4 bytes. E.g. `[192, 168, 0, 1]`
+/// * `<port>` is any unsigned 16-bit value.
+///
+/// ## Example
+/// `{"ip": [192, 168,0, 1], "port": 1111}`
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
+pub struct StreamTarget {
+ pub ip: [u8; 4],
+ pub port: u16,
+}
+
+/// Specifies the format of streamed data
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive)]
+pub enum StreamFormat {
+ /// Reserved, unused format specifier.
+ Unknown = 0,
+
+ /// Streamed data contains ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format.
+ ///
+ /// # Example
+ /// With a batch size of 2, the serialization would take the following form:
+ /// ```
+ /// <ADC0[0]> <ADC0[1]> <ADC1[0]> <ADC1[1]> <DAC0[0]> <DAC0[1]> <DAC1[0]> <DAC1[1]>
+ /// ```
+ AdcDacData = 1,
+
+ /// Streamed data in FLS (fiber length stabilization) format. See the FLS application for
+ /// detailed definition.
+ Fls = 2,
+}
+
+impl From<StreamTarget> for SocketAddr {
+ fn from(target: StreamTarget) -> SocketAddr {
+ SocketAddr::new(
+ IpAddr::V4(Ipv4Addr::new(
+ target.ip[0],
+ target.ip[1],
+ target.ip[2],
+ target.ip[3],
+ )),
+ target.port,
+ )
+ }
+}
+
+/// Configure streaming on a device.
+///
+/// # Args
+/// * `stack` - A reference to the shared network stack.
+///
+/// # Returns
+/// (generator, stream) where `generator` can be used to enqueue "batches" for transmission. The
+/// `stream` is the logically consumer (UDP transmitter) of the enqueued data.
+pub fn setup_streaming(
+ stack: NetworkReference,
+) -> (FrameGenerator, DataStream) {
+ // The queue needs to be at least as large as the frame count to ensure that every allocated
+ // frame can potentially be enqueued for transmission.
+ let queue =
+ cortex_m::singleton!(: Queue<StreamFrame, FRAME_QUEUE_SIZE> = Queue::new())
+ .unwrap();
+ let (producer, consumer) = queue.split();
+
+ let frame_pool = cortex_m::singleton!(: Pool<Frame> = Pool::new()).unwrap();
+
+ // Note(unsafe): We guarantee that FRAME_DATA is only accessed once in this function.
+ let memory = unsafe { &mut FRAME_DATA };
+ frame_pool.grow(memory);
+
+ let generator = FrameGenerator::new(producer, frame_pool);
+
+ let stream = DataStream::new(stack, consumer, frame_pool);
+
+ (generator, stream)
+}
+
+#[derive(Debug)]
+struct StreamFrame {
+ buffer: Box<Frame, Init>,
+ offset: usize,
+ batches: u8,
+}
+
+impl StreamFrame {
+ pub fn new(
+ buffer: Box<Frame, Uninit>,
+ format_id: u8,
+ sequence_number: u32,
+ ) -> Self {
+ let mut buffer = buffer.init([MaybeUninit::uninit(); FRAME_SIZE]);
+
+ for (byte, buf) in MAGIC
+ .to_le_bytes()
+ .iter()
+ .chain(&[format_id, 0])
+ .chain(sequence_number.to_le_bytes().iter())
+ .zip(buffer.iter_mut())
+ {
+ buf.write(*byte);
+ }
+
+ Self {
+ buffer,
+ offset: HEADER_SIZE,
+ batches: 0,
+ }
+ }
+
+ pub fn add_batch<F>(&mut self, mut f: F) -> usize
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]) -> usize,
+ {
+ let len = f(&mut self.buffer[self.offset..]);
+ self.offset += len;
+ self.batches += 1;
+ len
+ }
+
+ pub fn is_full(&self, len: usize) -> bool {
+ self.offset + len > self.buffer.len()
+ }
+
+ pub fn finish(&mut self) -> &[MaybeUninit<u8>] {
+ self.buffer[3].write(self.batches);
+ &self.buffer[..self.offset]
+ }
+}
+
+/// The data generator for a stream.
+pub struct FrameGenerator {
+ queue: Producer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ pool: &'static Pool<Frame>,
+ current_frame: Option<StreamFrame>,
+ sequence_number: u32,
+ format: u8,
+}
+
+impl FrameGenerator {
+ fn new(
+ queue: Producer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ pool: &'static Pool<Frame>,
+ ) -> Self {
+ Self {
+ queue,
+ pool,
+ format: StreamFormat::Unknown.into(),
+ current_frame: None,
+ sequence_number: 0,
+ }
+ }
+
+ /// Configure the format of the stream.
+ ///
+ /// # Note:
+ /// This function shall only be called once upon initializing streaming
+ ///
+ /// # Args
+ /// * `format` - The desired format of the stream.
+ #[doc(hidden)]
+ pub(crate) fn configure(&mut self, format: impl Into<u8>) {
+ self.format = format.into();
+ }
+
+ /// Add a batch to the current stream frame.
+ ///
+ /// # Args
+ /// * `f` - A closure that will be provided the buffer to write batch data into.
+ /// Returns the number of bytes written.
+ pub fn add<F>(&mut self, f: F)
+ where
+ F: FnMut(&mut [MaybeUninit<u8>]) -> usize,
+ {
+ let sequence_number = self.sequence_number;
+ self.sequence_number = self.sequence_number.wrapping_add(1);
+
+ if self.current_frame.is_none() {
+ if let Some(buffer) = self.pool.alloc() {
+ self.current_frame.replace(StreamFrame::new(
+ buffer,
+ self.format,
+ sequence_number,
+ ));
+ } else {
+ return;
+ }
+ }
+
+ // Note(unwrap): We ensure the frame is present above.
+ let current_frame = self.current_frame.as_mut().unwrap();
+
+ let len = current_frame.add_batch(f);
+
+ if current_frame.is_full(len) {
+ // Note(unwrap): The queue is designed to be at least as large as the frame buffer
+ // count, so this enqueue should always succeed.
+ self.queue
+ .enqueue(self.current_frame.take().unwrap())
+ .unwrap();
+ }
+ }
+}
+
+/// The "consumer" portion of the data stream.
+///
+/// # Note
+/// This is responsible for consuming data and sending it over UDP.
+pub struct DataStream {
+ stack: NetworkReference,
+ socket: Option<<NetworkReference as UdpClientStack>::UdpSocket>,
+ queue: Consumer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ frame_pool: &'static Pool<Frame>,
+ remote: SocketAddr,
+}
+
+impl DataStream {
+ /// Construct a new data streamer.
+ ///
+ /// # Args
+ /// * `stack` - A reference to the shared network stack.
+ /// * `consumer` - The read side of the queue containing data to transmit.
+ /// * `frame_pool` - The Pool to return stream frame objects into.
+ fn new(
+ stack: NetworkReference,
+ consumer: Consumer<'static, StreamFrame, FRAME_QUEUE_SIZE>,
+ frame_pool: &'static Pool<Frame>,
+ ) -> Self {
+ Self {
+ stack,
+ socket: None,
+ remote: StreamTarget::default().into(),
+ queue: consumer,
+ frame_pool,
+ }
+ }
+
+ fn close(&mut self) {
+ if let Some(socket) = self.socket.take() {
+ log::info!("Closing stream");
+ // Note(unwrap): We guarantee that the socket is available above.
+ self.stack.close(socket).unwrap();
+ }
+ }
+
+ // Open new socket.
+ fn open(&mut self) -> Result<(), ()> {
+ // If there is already a socket of if remote address is unspecified,
+ // do not open a new socket.
+ if self.socket.is_some() || self.remote.ip().is_unspecified() {
+ return Err(());
+ }
+
+ log::info!("Opening stream");
+
+ let mut socket = self.stack.socket().or(Err(()))?;
+
+ // Note(unwrap): We only connect with a new socket, so it is guaranteed to not already be
+ // bound.
+ self.stack.connect(&mut socket, self.remote).unwrap();
+
+ self.socket.replace(socket);
+
+ Ok(())
+ }
+
+ /// Configure the remote endpoint of the stream.
+ ///
+ /// # Args
+ /// * `remote` - The destination to send stream data to.
+ pub fn set_remote(&mut self, remote: SocketAddr) {
+ // Close socket to be reopened if the remote has changed.
+ if remote != self.remote {
+ self.close();
+ }
+ self.remote = remote;
+ }
+
+ /// Process any data for transmission.
+ pub fn process(&mut self) {
+ match self.socket.as_mut() {
+ None => {
+ // If there's no socket available, try to connect to our remote.
+ if self.open().is_ok() {
+ // If we just successfully opened the socket, flush old data from queue.
+ while let Some(frame) = self.queue.dequeue() {
+ self.frame_pool.free(frame.buffer);
+ }
+ }
+ }
+ Some(handle) => {
+ if let Some(mut frame) = self.queue.dequeue() {
+ // Transmit the frame and return it to the pool.
+ let buf = frame.finish();
+ let data = unsafe {
+ core::slice::from_raw_parts(
+ buf.as_ptr() as *const u8,
+ core::mem::size_of_val(buf),
+ )
+ };
+ self.stack.send(handle, data).ok();
+ self.frame_pool.free(frame.buffer)
+ }
+ }
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +
//! 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::{
+ metadata::ApplicationMetadata, EthernetPhy, NetworkManager, NetworkStack,
+ SystemTimer,
+};
+use data_stream::{DataStream, FrameGenerator};
+use network_processor::NetworkProcessor;
+use telemetry::TelemetryClient;
+
+use core::fmt::Write;
+use heapless::String;
+use miniconf::JsonCoreSlash;
+use serde::Serialize;
+use smoltcp_nal::embedded_nal::SocketAddr;
+
+pub type NetworkReference =
+ smoltcp_nal::shared::NetworkStackProxy<'static, NetworkStack>;
+
+pub struct MqttStorage {
+ telemetry: [u8; 2048],
+ settings: [u8; 1024],
+}
+
+impl Default for MqttStorage {
+ fn default() -> Self {
+ Self {
+ telemetry: [0u8; 2048],
+ settings: [0u8; 1024],
+ }
+ }
+}
+
+pub enum UpdateState {
+ NoChange,
+ Updated,
+}
+
+pub enum NetworkState {
+ SettingsChanged(String<128>),
+ Updated,
+ NoChange,
+}
+
+/// A structure of Stabilizer's default network users.
+pub struct NetworkUsers<S, T, const Y: usize>
+where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,
+{
+ pub miniconf: miniconf::MqttClient<
+ 'static,
+ S,
+ NetworkReference,
+ SystemTimer,
+ miniconf::minimq::broker::NamedBroker<NetworkReference>,
+ Y,
+ >,
+ pub processor: NetworkProcessor,
+ stream: DataStream,
+ generator: Option<FrameGenerator>,
+ pub telemetry: TelemetryClient<T>,
+}
+
+impl<S, T, const Y: usize> NetworkUsers<S, T, Y>
+where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,
+{
+ /// Construct Stabilizer's default network users.
+ ///
+ /// # Args
+ /// * `stack` - The network stack that will be used to share with all network users.
+ /// * `phy` - The ethernet PHY connecting the network.
+ /// * `clock` - A `SystemTimer` implementing `Clock`.
+ /// * `app` - The name of the application.
+ /// * `broker` - The domain name of the MQTT broker to use.
+ /// * `id` - The MQTT client ID base to use.
+ /// * `metadata` - The application metadata
+ ///
+ /// # Returns
+ /// A new struct of network users.
+ pub fn new(
+ stack: NetworkStack,
+ phy: EthernetPhy,
+ clock: SystemTimer,
+ app: &str,
+ broker: &str,
+ id: &str,
+ metadata: &'static ApplicationMetadata,
+ ) -> 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, id);
+
+ let store =
+ cortex_m::singleton!(: MqttStorage = MqttStorage::default())
+ .unwrap();
+
+ let named_broker = miniconf::minimq::broker::NamedBroker::new(
+ broker,
+ stack_manager.acquire_stack(),
+ )
+ .unwrap();
+ let settings = miniconf::MqttClient::new(
+ stack_manager.acquire_stack(),
+ &prefix,
+ clock,
+ S::default(),
+ miniconf::minimq::ConfigBuilder::new(
+ named_broker,
+ &mut store.settings,
+ )
+ .client_id(&get_client_id(id, "settings"))
+ .unwrap(),
+ )
+ .unwrap();
+
+ let named_broker = minimq::broker::NamedBroker::new(
+ broker,
+ stack_manager.acquire_stack(),
+ )
+ .unwrap();
+ let mqtt = minimq::Minimq::new(
+ stack_manager.acquire_stack(),
+ clock,
+ minimq::ConfigBuilder::new(named_broker, &mut store.telemetry)
+ // The telemetry client doesn't receive any messages except MQTT control packets.
+ // As such, we don't need much of the buffer for RX.
+ .rx_buffer(minimq::config::BufferConfig::Maximum(100))
+ .client_id(&get_client_id(id, "tlm"))
+ .unwrap(),
+ );
+
+ let telemetry = TelemetryClient::new(mqtt, &prefix, metadata);
+
+ let (generator, stream) =
+ data_stream::setup_streaming(stack_manager.acquire_stack());
+
+ NetworkUsers {
+ miniconf: settings,
+ processor,
+ telemetry,
+ stream,
+ generator: Some(generator),
+ }
+ }
+
+ /// Enable live data streaming.
+ ///
+ /// # Args
+ /// * `format` - A unique u8 code indicating the format of the data.
+ pub fn configure_streaming(
+ &mut self,
+ format: impl Into<u8>,
+ ) -> FrameGenerator {
+ let mut generator = self.generator.take().unwrap();
+ generator.configure(format);
+ generator
+ }
+
+ /// Direct the stream to the provided remote target.
+ ///
+ /// # Args
+ /// * `remote` - The destination for the streamed data.
+ pub fn direct_stream(&mut self, remote: SocketAddr) {
+ if self.generator.is_none() {
+ self.stream.set_remote(remote);
+ }
+ }
+
+ /// Update and process all of the network users state.
+ ///
+ /// # Returns
+ /// An indication if any of the network users indicated a state change.
+ /// The SettingsChanged option contains the path of the settings that changed.
+ pub fn update(&mut self) -> NetworkState {
+ // Update the MQTT clients.
+ self.telemetry.update();
+
+ // Update the data stream.
+ if self.generator.is_none() {
+ self.stream.process();
+ }
+
+ // Poll for incoming data.
+ let poll_result = match self.processor.update() {
+ UpdateState::NoChange => NetworkState::NoChange,
+ UpdateState::Updated => NetworkState::Updated,
+ };
+
+ // `settings_path` has to be at least as large as `miniconf::mqtt_client::MAX_TOPIC_LENGTH`.
+ let mut settings_path: String<128> = String::new();
+ match self.miniconf.handled_update(|path, old, new| {
+ settings_path = path.into();
+ *old = new.clone();
+ Result::<(), &'static str>::Ok(())
+ }) {
+ Ok(true) => NetworkState::SettingsChanged(settings_path),
+ _ => poll_result,
+ }
+ }
+}
+
+/// Get an MQTT client ID for a client.
+///
+/// # Args
+/// * `id` - The base client ID
+/// * `mode` - The operating mode of this client. (i.e. tlm, settings)
+///
+/// # Returns
+/// A client ID that may be used for MQTT client identification.
+fn get_client_id(id: &str, mode: &str) -> String<64> {
+ let mut identifier = String::new();
+ write!(&mut identifier, "{id}-{mode}").unwrap();
+ identifier
+}
+
+/// Get the MQTT prefix of a device.
+///
+/// # Args
+/// * `app` - The name of the application that is executing.
+/// * `id` - The MQTT ID of the device.
+///
+/// # Returns
+/// The MQTT prefix used for this device.
+pub fn get_device_prefix(app: &str, id: &str) -> 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}/{id}").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 +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 +
//! 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 crate::hardware::metadata::ApplicationMetadata;
+use heapless::{String, Vec};
+use minimq::{DeferredPublication, Publication};
+use serde::Serialize;
+
+use super::NetworkReference;
+use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode, SystemTimer};
+
+/// Default metadata message if formatting errors occur.
+const DEFAULT_METADATA: &str = "{\"message\":\"Truncated: See USB terminal\"}";
+
+/// The telemetry client for reporting telemetry data over MQTT.
+pub struct TelemetryClient<T: Serialize> {
+ mqtt: minimq::Minimq<
+ 'static,
+ NetworkReference,
+ SystemTimer,
+ minimq::broker::NamedBroker<NetworkReference>,
+ >,
+ prefix: String<128>,
+ meta_published: bool,
+ _telemetry: core::marker::PhantomData<T>,
+ metadata: &'static ApplicationMetadata,
+}
+
+/// The telemetry buffer is used for storing sample values during execution.
+///
+/// # Note
+/// These values can be converted to SI units immediately before reporting to save processing time.
+/// This allows for the DSP process to continually update the values without incurring significant
+/// run-time overhead during conversion to SI units.
+#[derive(Copy, Clone)]
+pub struct TelemetryBuffer {
+ /// The latest input sample on ADC0/ADC1.
+ pub adcs: [AdcCode; 2],
+ /// The latest output code on DAC0/DAC1.
+ pub dacs: [DacCode; 2],
+ /// The latest digital input states during processing.
+ pub digital_inputs: [bool; 2],
+}
+
+/// The telemetry structure is data that is ultimately reported as telemetry over MQTT.
+///
+/// # Note
+/// This structure should be generated on-demand by the buffer when required to minimize conversion
+/// overhead.
+#[derive(Serialize)]
+pub struct Telemetry {
+ /// Most recent input voltage measurement.
+ pub adcs: [f32; 2],
+
+ /// Most recent output voltage.
+ pub dacs: [f32; 2],
+
+ /// Most recent digital input assertion state.
+ pub digital_inputs: [bool; 2],
+
+ /// The CPU temperature in degrees Celsius.
+ pub cpu_temp: f32,
+}
+
+impl Default for TelemetryBuffer {
+ fn default() -> Self {
+ Self {
+ adcs: [AdcCode(0), AdcCode(0)],
+ dacs: [DacCode(0), DacCode(0)],
+ digital_inputs: [false, false],
+ }
+ }
+}
+
+impl TelemetryBuffer {
+ /// Convert the telemetry buffer to finalized, SI-unit telemetry for reporting.
+ ///
+ /// # Args
+ /// * `afe0` - The current AFE configuration for channel 0.
+ /// * `afe1` - The current AFE configuration for channel 1.
+ /// * `cpu_temp` - The current CPU temperature.
+ ///
+ /// # Returns
+ /// The finalized telemetry structure that can be serialized and reported.
+ pub fn finalize(self, afe0: Gain, afe1: Gain, cpu_temp: f32) -> Telemetry {
+ let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
+ let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
+
+ Telemetry {
+ cpu_temp,
+ adcs: [in0_volts, in1_volts],
+ dacs: [self.dacs[0].into(), self.dacs[1].into()],
+ digital_inputs: self.digital_inputs,
+ }
+ }
+}
+
+impl<T: Serialize> TelemetryClient<T> {
+ /// Construct a new telemetry client.
+ ///
+ /// # Args
+ /// * `mqtt` - The MQTT client
+ /// * `prefix` - The device prefix to use for MQTT telemetry reporting.
+ ///
+ /// # Returns
+ /// A new telemetry client.
+ pub fn new(
+ mqtt: minimq::Minimq<
+ 'static,
+ NetworkReference,
+ SystemTimer,
+ minimq::broker::NamedBroker<NetworkReference>,
+ >,
+ prefix: &str,
+ metadata: &'static ApplicationMetadata,
+ ) -> Self {
+ Self {
+ mqtt,
+ meta_published: false,
+ prefix: String::from(prefix),
+ _telemetry: core::marker::PhantomData,
+ metadata,
+ }
+ }
+
+ /// 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 mut topic = self.prefix.clone();
+ topic.push_str("/telemetry").unwrap();
+
+ let telemetry: Vec<u8, 512> =
+ serde_json_core::to_vec(telemetry).unwrap();
+
+ self.mqtt
+ .client()
+ .publish(
+ minimq::Publication::<&[u8]>::new(&telemetry)
+ .topic(&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),
+ _ => {}
+ }
+
+ if !self.mqtt.client().is_connected() {
+ self.meta_published = false;
+ return;
+ }
+
+ // Publish application metadata
+ if !self.meta_published
+ && self.mqtt.client().can_publish(minimq::QoS::AtMostOnce)
+ {
+ let Self {
+ ref mut mqtt,
+ metadata,
+ ..
+ } = self;
+
+ let mut topic = self.prefix.clone();
+ topic.push_str("/meta").unwrap();
+
+ if mqtt
+ .client()
+ .publish(
+ DeferredPublication::new(|buf| {
+ serde_json_core::to_slice(&metadata, buf)
+ })
+ .topic(&topic)
+ .finish()
+ .unwrap(),
+ )
+ .is_err()
+ {
+ // Note(unwrap): We can guarantee that this message will be sent because we checked
+ // for ability to publish above.
+ mqtt.client()
+ .publish(
+ Publication::new(DEFAULT_METADATA.as_bytes())
+ .topic(&topic)
+ .finish()
+ .unwrap(),
+ )
+ .unwrap();
+ }
+
+ self.meta_published = 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 +
//! Stabilizer Settings Management
+//!
+//! # Design
+//! Stabilizer supports two types of settings:
+//! 1. Static Device Configuration
+//! 2. Dynamic Run-time Settings
+//!
+//! Static device configuration settings are loaded and used only at device power-up. These include
+//! things like the MQTT broker address and the MQTT identified. Conversely, the dynamic run-time
+//! settings can be changed and take effect immediately during device operation.
+//!
+//! This settings management interface is currently targeted at the static device configuration
+//! settings. Settings are persisted into the unused 1MB flash bank of Stabilizer for future
+//! recall. They can be modified via the USB interface to facilitate device configuration.
+//!
+//! Settings are stored in flash using a key-value pair mapping, where the `key` is the name of the
+//! entry in the settings structure. This has a number of benefits:
+//! 1. The `Settings` structure can have new entries added to it in the future without losing old
+//! settings values, as each entry of the `Settings` struct is stored separately as its own
+//! key-value pair.
+//! 2. The `Settings` can be used among multiple Stabilizer firmware versions that need the same
+//! settings values
+//! 3. Unknown/unneeded settings values in flash can be actively ignored, facilitating simple flash
+//! storage sharing.
+use crate::hardware::{flash::Flash, metadata::ApplicationMetadata, platform};
+use core::fmt::Write;
+use miniconf::{TreeDeserialize, TreeKey, TreeSerialize};
+use postcard::ser_flavors::Flavor;
+use stm32h7xx_hal::flash::LockedFlashBank;
+
+#[derive(Clone, miniconf::Tree)]
+pub struct Settings {
+ pub broker: heapless::String<255>,
+ pub id: heapless::String<23>,
+ #[tree(skip)]
+ pub mac: smoltcp_nal::smoltcp::wire::EthernetAddress,
+}
+
+impl serial_settings::Settings for Settings {
+ fn reset(&mut self) {
+ *self = Self::new(self.mac)
+ }
+}
+
+impl Settings {
+ pub fn new(mac: smoltcp_nal::smoltcp::wire::EthernetAddress) -> Self {
+ let mut id = heapless::String::new();
+ write!(&mut id, "{mac}").unwrap();
+
+ Self {
+ broker: "mqtt".into(),
+ id,
+ mac,
+ }
+ }
+
+ pub fn reload(&mut self, storage: &mut Flash) {
+ // Loop over flash and read settings
+ let mut buffer = [0u8; 512];
+ for path in Settings::iter_paths::<heapless::String<32>>("/") {
+ let path = path.unwrap();
+
+ // Try to fetch the setting from flash.
+ let Some(item) =
+ sequential_storage::map::fetch_item::<SettingsItem, _>(
+ storage,
+ storage.range(),
+ &mut buffer,
+ path.clone(),
+ )
+ .unwrap()
+ else {
+ continue;
+ };
+
+ log::info!("Found `{path}` in flash settings");
+
+ let mut deserializer = postcard::Deserializer::from_flavor(
+ postcard::de_flavors::Slice::new(&item.data),
+ );
+ if let Err(e) = self
+ .deserialize_by_key(path.split('/').skip(1), &mut deserializer)
+ {
+ log::warn!("Failed to load {path} from flash settings: {e:?}");
+ }
+ }
+ }
+}
+
+#[derive(Default, serde::Serialize, serde::Deserialize)]
+pub struct SettingsItem {
+ // We only make these owned vec/string to get around lifetime limitations.
+ pub path: heapless::String<32>,
+ pub data: heapless::Vec<u8, 256>,
+}
+
+impl sequential_storage::map::StorageItem for SettingsItem {
+ type Key = heapless::String<32>;
+ type Error = postcard::Error;
+
+ fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
+ Ok(postcard::to_slice(self, buffer)?.len())
+ }
+
+ fn deserialize_from(buffer: &[u8]) -> Result<Self, Self::Error> {
+ postcard::from_bytes(buffer)
+ }
+
+ fn key(&self) -> Self::Key {
+ self.path.clone()
+ }
+}
+
+#[derive(Debug)]
+pub enum Error<F> {
+ Postcard(postcard::Error),
+ Flash(F),
+}
+
+impl<F> From<postcard::Error> for Error<F> {
+ fn from(e: postcard::Error) -> Self {
+ Self::Postcard(e)
+ }
+}
+
+pub struct SerialSettingsPlatform {
+ /// The interface to read/write data to/from serially (via text) to the user.
+ pub interface: serial_settings::BestEffortInterface<
+ usbd_serial::SerialPort<'static, crate::hardware::UsbBus>,
+ >,
+ /// The Settings structure.
+ pub settings: Settings,
+ /// The storage mechanism used to persist settings to between boots.
+ pub storage: Flash,
+
+ /// Metadata associated with the application
+ pub metadata: &'static ApplicationMetadata,
+}
+
+impl serial_settings::Platform for SerialSettingsPlatform {
+ type Interface = serial_settings::BestEffortInterface<
+ usbd_serial::SerialPort<'static, crate::hardware::UsbBus>,
+ >;
+ type Settings = Settings;
+ type Error = Error<
+ <LockedFlashBank as embedded_storage::nor_flash::ErrorType>::Error,
+ >;
+
+ fn save(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
+ for path in Settings::iter_paths::<heapless::String<32>>("/") {
+ let mut item = SettingsItem {
+ path: path.unwrap(),
+ ..Default::default()
+ };
+
+ item.data.resize(item.data.capacity(), 0).unwrap();
+
+ let mut serializer = postcard::Serializer {
+ output: postcard::ser_flavors::Slice::new(&mut item.data),
+ };
+
+ if let Err(e) = self
+ .settings
+ .serialize_by_key(item.path.split('/').skip(1), &mut serializer)
+ {
+ log::warn!("Failed to save {} to flash: {e:?}", item.path);
+ continue;
+ }
+
+ let len = serializer.output.finalize()?.len();
+ item.data.truncate(len);
+
+ let range = self.storage.range();
+
+ // Check if the settings has changed from what's currently in flash (or if it doesn't
+ // yet exist).
+ if sequential_storage::map::fetch_item::<SettingsItem, _>(
+ &mut self.storage,
+ range.clone(),
+ buf,
+ item.path.clone(),
+ )
+ .unwrap()
+ .map(|old| old.data != item.data)
+ .unwrap_or(true)
+ {
+ log::info!("Storing setting `{}` in flash", item.path);
+ sequential_storage::map::store_item(
+ &mut self.storage,
+ range,
+ buf,
+ item,
+ )
+ .unwrap();
+ }
+ }
+
+ Ok(())
+ }
+
+ fn cmd(&mut self, cmd: &str) {
+ match cmd {
+ "reboot" => cortex_m::peripheral::SCB::sys_reset(),
+ "dfu" => platform::start_dfu_reboot(),
+ "service" => {
+ writeln!(
+ &mut self.interface,
+ "{:<20}: {} [{}]",
+ "Version",
+ self.metadata.firmware_version,
+ self.metadata.profile,
+ )
+ .unwrap();
+ writeln!(
+ &mut self.interface,
+ "{:<20}: {}",
+ "Hardware Revision", self.metadata.hardware_version
+ )
+ .unwrap();
+ writeln!(
+ &mut self.interface,
+ "{:<20}: {}",
+ "Rustc Version", self.metadata.rust_version
+ )
+ .unwrap();
+ writeln!(
+ &mut self.interface,
+ "{:<20}: {}",
+ "Features", self.metadata.features
+ )
+ .unwrap();
+ writeln!(
+ &mut self.interface,
+ "{:<20}: {}",
+ "Panic Info", self.metadata.panic_info
+ )
+ .unwrap();
+ }
+ _ => {
+ writeln!(
+ self.interface_mut(),
+ "Invalid platform command: `{cmd}` not in [`dfu`, `reboot`, `service`]"
+ )
+ .ok();
+ }
+ }
+ }
+
+ fn settings(&self) -> &Self::Settings {
+ &self.settings
+ }
+
+ fn settings_mut(&mut self) -> &mut Self::Settings {
+ &mut self.settings
+ }
+
+ fn interface_mut(&mut self) -> &mut Self::Interface {
+ &mut self.interface
+ }
+}
+
Stabilizer ADC management interface
+Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs +require a setup conversion time after asserting the CSn (convert) signal to generate the ADC +code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of +MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter +during SPI configuration, which allows offloading the management of the setup time to hardware.
+Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate +the collection of multiple ADC samples without requiring processing by the CPU, which reduces +overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
+The automation of sample collection utilizes three DMA streams, the SPI peripheral, and two +timer compare channel for each ADC. One timer comparison channel is configured to generate a +comparison event every time the timer is equal to a specific value. Each comparison then +generates a DMA transfer event to write into the SPI CR1 register to initiate the transfer. +This allows the SPI interface to periodically read a single sample. The other timer comparison +channel is configured to generate a comparison event slightly before the first (~10 timer +cycles). This channel triggers a separate DMA stream to clear the EOT flag within the SPI +peripheral. The EOT flag must be cleared after each transfer or the SPI peripheral will not +properly complete the single conversion. Thus, by using two DMA streams and timer comparison +channels, the SPI can regularly acquire ADC samples.
+In order to collect the acquired ADC samples into a RAM buffer, a final DMA transfer is +configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to +the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is +available. When enough samples have been collected, a transfer-complete interrupt is generated +and the ADC samples are available for processing.
+After a complete transfer of a batch of samples, the inactive buffer is available to the +user for processing. The processing must complete before the DMA transfer of the next batch +completes.
+Because the DMA data collection is automated via timer count comparisons and DMA transfers, the +ADCs can be initialized and configured, but will not begin sampling the external ADCs until the +sampling timer is enabled. As such, the sampling timer should be enabled after all +initialization has completed and immediately before the embedded processing loop begins.
+The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch +is configured by the user at compile-time to allow for a custom-tailored implementation. Larger +batch sizes generally provide for lower overhead and more processing time per sample, but come +at the expense of increased input -> output latency.
+While there are two ADCs, only a single ADC is configured to generate transfer-complete +interrupts. This is done because it is assumed that the ADCs will always be sampled +simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate +transfer-complete interrupts.
+There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As +such, one of the ADCs will be sampled marginally earlier before the other because the DMA +requests are generated simultaneously. This can be avoided by providing a known offset to the +sample DMA requests, which can be completed by setting e.g. ADC0’s comparison to a counter +value of 0 and ADC1’s comparison to a counter value of 1.
+In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs +have finite depth, FIFO access is slower than AXISRAM access, and because the single +buffer mode DMA disable/enable and buffer update sequence is slow.
+pub struct Adc0Input { /* private fields */ }
Represents data associated with ADC.
+Construct the ADC input channel.
+spi
- The SPI interface used to communicate with the ADC.trigger_stream
- The DMA stream used to trigger each ADC transfer by
+writing a word into the SPI TX FIFO.data_stream
- The DMA stream used to read samples received over SPI into a data buffer.clear_stream
- The DMA stream used to clear the EOT flag in the SPI peripheral.trigger_channel
- The ADC sampling timer output compare channel for read triggers.clear_channel
- The shadow sampling timer output compare channel used for
+clearing the SPI EOT flag.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct Adc1Input { /* private fields */ }
Represents data associated with ADC.
+Construct the ADC input channel.
+spi
- The SPI interface used to communicate with the ADC.trigger_stream
- The DMA stream used to trigger each ADC transfer by
+writing a word into the SPI TX FIFO.data_stream
- The DMA stream used to read samples received over SPI into a data buffer.clear_stream
- The DMA stream used to clear the EOT flag in the SPI peripheral.trigger_channel
- The ADC sampling timer output compare channel for read triggers.clear_channel
- The shadow sampling timer output compare channel used for
+clearing the SPI EOT flag.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct AdcCode(pub u16);
A type representing an ADC sample.
+0: u16
#[repr(u8)]pub enum Gain {
+ G1 = 0,
+ G2 = 1,
+ G5 = 2,
+ G10 = 3,
+}
pub struct ProgrammableGainAmplifier<A0, A1> { /* private fields */ }
A programmable gain amplifier that allows for setting the gain via GPIO.
+pub const MONOTONIC_FREQUENCY: u32 = 1_000;
System timer (RTIC Monotonic) tick frequency
+STM32 Temperature Sensor Driver
+This file provides an API for measuring the internal STM32 temperature sensor. This temperature +sensor measures the silicon junction temperature (Tj) and is connected via an internal ADC.
+pub struct CpuTempSensor { /* private fields */ }
A driver to access the CPU temeprature sensor.
+Construct the temperature sensor.
+sensor
- The ADC channel of the integrated temperature sensor.Get the temperature of the CPU in degrees Celsius.
+Stabilizer DAC management interface
+Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC +accepts a 16-bit output code.
+In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using +a timer compare channel, DMA stream, and the DAC SPI interface.
+The timer comparison channel is configured to generate a DMA request whenever the comparison +occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By +configuring a DMA stream for a number of successive DAC codes, hardware can regularly update +the DAC without requiring the CPU.
+In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC +output batch is always exactly 3 batches after the ADC batch that generated it.
+The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any +transfer events generated by the timer (for example, when 2 update cycles occur before the DMA +transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the +“next-transfer” double-buffer location for the DMA transfer. Once a transfer completes, +software then has exactly one batch duration to fill the next buffer before its +transfer begins. If software does not meet this deadline, old data will be repeatedly generated +on the output and output will be shifted by one batch.
+For some applications, it may be desirable to generate a single DAC code from multiple ADC +samples. In order to maintain timing characteristics between ADC samples and DAC code outputs, +applications are required to generate one DAC code for each ADC sample. To accomodate mapping +multiple inputs to a single output, the output code can be repeated a number of times in the +output buffer corresponding with the number of input samples that were used to generate it.
+There is a very small amount of latency between updating the two DACs due to bus matrix +priority. As such, one of the DACs will be updated marginally earlier before the other because +the DMA requests are generated simultaneously. This can be avoided by providing a known offset +to other DMA requests, which can be completed by setting e.g. DAC0’s comparison to a +counter value of 2 and DAC1’s comparison to a counter value of 3. This will have the effect of +generating the DAC updates with a known latency of 1 timer tick to each other and prevent the +DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the +DMA channels to arbitrate which transfer occurs first.
+While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check +for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is +served promptly after the transfer completes.
+pub struct Dac0Output { /* private fields */ }
Represents data associated with DAC.
+Construct the DAC output channel.
+spi
- The SPI interface used to communicate with the ADC.stream
- The DMA stream used to write DAC codes over SPI.trigger_channel
- The sampling timer output compare channel for update triggers.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct Dac1Output { /* private fields */ }
Represents data associated with DAC.
+Construct the DAC output channel.
+spi
- The SPI interface used to communicate with the ADC.stream
- The DMA stream used to write DAC codes over SPI.trigger_channel
- The sampling timer output compare channel for update triggers.Wait for the transfer of the currently active buffer to complete, +then call a function on the now inactive buffer and acknowledge the +transfer complete flag.
+NOTE(unsafe): Memory safety and access ordering is not guaranteed +(see the HAL DMA docs).
+pub struct DacCode(pub u16);
Custom type for referencing DAC output codes. +The internal integer is the raw code written to the DAC output register.
+0: u16
Basic blocking delay
+This module provides a basic asm-based blocking delay.
+This implementation takes into account the Cortex-M7 CPU pipeline architecture to ensure delays +are at least as long as specified.
+pub struct AsmDelay { /* private fields */ }
A basic delay implementation.
+pub const ADC_DAC_SCK_MAX: MegaHertz;
The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
+pub const ADC_SETUP_TIME: f32 = 220e-9;
The ADC setup time is the number of seconds after the CSn line goes low before the serial clock +may begin. This is used for performing the internal ADC conversion.
+pub const DDS_MULTIPLIER: u8 = 5;
The multiplier used for the DDS reference clock PLL.
+pub const DDS_REF_CLK: MegaHertz;
The DDS reference clock frequency in MHz.
+pub const DDS_SYNC_CLK_DIV: u8 = 4;
The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
+pub const DDS_SYSTEM_CLK: MegaHertz;
The DDS system clock frequency after the internal PLL multiplication.
+pub const MAX_SAMPLE_BUFFER_SIZE: usize = 32;
The maximum ADC/DAC sample processing buffer size.
+pub const POUNDER_IO_UPDATE_DELAY: f32 = 1_300e-9;
The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS.
+pub const POUNDER_IO_UPDATE_DURATION: f32 = 50e-9;
The duration to assert IO_Update for the pounder DDS.
+pub const POUNDER_QSPI_FREQUENCY: MegaHertz;
The QSPI frequency for communicating with the pounder DDS.
+pub const SYSCLK: MegaHertz;
The system clock, used in various timer calculations
+pub const TIMER_FREQUENCY: MegaHertz;
The optimal counting frequency of the hardware timers used for timestamping and sampling.
+pub const TIMER_PERIOD: f32 = _; // 9.99999993E-9f32
pub type SampleBuffer = [u16; 32];
pub enum HardwareVersion {
+ Rev1_0,
+ Rev1_1,
+ Rev1_2,
+ Rev1_3,
+ Unknown(u8),
+}
pub struct Flash(pub LockedFlashBank);
0: LockedFlashBank
Module for all hardware-specific setup of Stabilizer
+pub use embedded_hal;
pub use stm32h7xx_hal as hal;
Digital Input 0 (DI0) reference clock timestamper
+This module provides a means of timestamping the rising edges of an external reference clock on +the DI0 with a timer value from TIM5.
+An input capture channel is configured on DI0 and fed into TIM5’s capture channel 4. TIM5 is +then run in a free-running mode with a configured tick rate (PSC) and maximum count value +(ARR). Whenever an edge on DI0 triggers, the current TIM5 counter value is captured and +recorded as a timestamp. This timestamp can be either directly read from the timer channel or +can be collected asynchronously via DMA collection.
+To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is +continually checked. Any over-capture event (which indicates an overwritten timestamp) then +triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted.
+It appears that DMA transfers can take a significant amount of time to disable (400ns) if they +are being prematurely stopped (such is the case here). As such, for a sample batch size of 1, +this can take up a significant amount of the total available processing time for the samples. +This module checks for any captured timestamps from the timer capture channel manually. In +this mode, the maximum input clock frequency supported is dependant on the sampling rate and +batch size.
+This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If +timestamping is desired in DI1, a separate timer + capture channel will be necessary.
+pub struct InputStamper { /* private fields */ }
The timestamper for DI0 reference clock inputs.
+Construct the DI0 input timestamper.
+trigger
- The capture trigger input pin.Get the latest timestamp that has occurred.
+This function must be called at least as often as timestamps arrive.
+If an over-capture event occurs, this function will clear the overflow,
+and return a new timestamp of unknown recency an Err()
.
+Note that this indicates at least one timestamp was inadvertently dropped.
pub struct ApplicationMetadata {
+ pub firmware_version: &'static str,
+ pub rust_version: &'static str,
+ pub profile: &'static str,
+ pub git_dirty: bool,
+ pub features: &'static str,
+ pub panic_info: &'static str,
+ pub hardware_version: HardwareVersion,
+}
firmware_version: &'static str
§rust_version: &'static str
§profile: &'static str
§git_dirty: bool
§features: &'static str
§panic_info: &'static str
§hardware_version: HardwareVersion
pub fn dfu_bootflag() -> bool
Check if the DFU reboot flag is set, indicating a reboot to DFU is requested.
+pub fn execute_system_bootloader()
Execute the DFU bootloader stored in system memory.
+This function must be called before any system configuration is performed, as the DFU +bootloader expects the system in an uninitialized state.
+pub fn start_dfu_reboot()
Indicate a reboot to DFU is requested.
+pub trait AttenuatorInterface {
+ // Required methods
+ fn reset_attenuators(&mut self) -> Result<(), Error>;
+ fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error>;
+ fn transfer_attenuators(
+ &mut self,
+ channels: &mut [u8; 4]
+ ) -> Result<(), Error>;
+
+ // Provided methods
+ fn set_attenuation(
+ &mut self,
+ channel: Channel,
+ attenuation: f32
+ ) -> Result<f32, Error> { ... }
+ fn get_attenuation(&mut self, channel: Channel) -> Result<f32, Error> { ... }
+}
Provide an interface for managing digital attenuators on Pounder hardware.
+Note: The digital attenuators do not allow read-back of attenuation. To circumvent this, this +driver maintains the attenuation code in both the shift register as well as the latched output +register of the attenuators. This allows the “active” attenuation code to be read back by +reading the shfit register. The downside of this approach is that any read is destructive, so a +read-writeback approach is employed.
+Set the attenuation of a single channel.
+Args:
+channel
- The pounder channel to configure the attenuation of.attenuation
- The desired attenuation of the channel in dB. This has a resolution of
+0.5dB.The DdsOutput is used as an output stream to the pounder DDS.
+The DDS stream interface is a means of quickly updating pounder DDS (direct digital synthesis) +outputs of the AD9959 DDS chip. The DDS communicates via a quad-SPI interface and a single +IO-update output pin.
+In order to update the DDS interface, the frequency tuning word, amplitude control word, and +the phase offset word for a channel can be modified to change the frequency, amplitude, or +phase on any of the 4 available output channels. Changes do not propagate to DDS outputs until +the IO-update pin is toggled high to activate the new configurations. This allows multiple +channels or parameters to be updated and then effects can take place simultaneously.
+In this implementation, the phase, frequency, or amplitude can be updated for any single +collection of outputs simultaneously. This is done by serializing the register writes to the +DDS into a single buffer of data and then writing the data over QSPI to the DDS.
+In order to minimize software overhead, data is written directly into the QSPI output FIFO. In +order to accomplish this most efficiently, serialized data is written as 32-bit words to +minimize the number of bus cycles necessary to write to the peripheral FIFO. A consequence of +this is that additional unneeded register writes may be appended to align a transfer to 32-bit +word sizes.
+In order to pulse the IO-update signal, the high-resolution timer output is used. The timer is +configured to assert the IO-update signal after a predefined delay and then de-assert the +signal after a predefined assertion duration. This allows for the actual QSPI transfer and +IO-update toggle to be completed asynchronously to the rest of software processing - that is, +software can schedule the DDS updates and then continue data processing. DDS updates then take +place in the future when the IO-update is toggled by hardware.
+The QSPI output FIFO is used as an intermediate buffer for holding pending QSPI writes. Because +of this, the implementation only supports up to 16 serialized bytes (the QSPI FIFO is 8 32-bit +words, or 32 bytes, wide) in a single update.
+There is currently no synchronization between completion of the QSPI data write and the +IO-update signal. It is currently assumed that the QSPI transfer will always complete within a +predefined delay (the pre-programmed IO-update timer delay).
+In the future, it would be possible to utilize a DMA transfer to complete the QSPI transfer. +Once the QSPI transfer completed, this could trigger the IO-update timer to start to +asynchronously complete IO-update automatically. This would allow for arbitrary profile sizes +and ensure that IO-update was in-sync with the QSPI transfer.
+Currently, serialization is performed on each processing cycle. If there is a +compile-time-known register update sequence needed for the application, the serialization +process can be done once and then register values can be written into a pre-computed serialized +buffer to avoid the software overhead of much of the serialization process.
+pub struct DdsOutput { /* private fields */ }
The DDS profile update stream.
+Construct a new DDS output stream.
+It is assumed that the QSPI stream and the IO_Update trigger timer have been configured in a +way such that the profile has sufficient time to be written before the IO_Update signal is +generated.
+qspi
- The QSPI interface to the run the stream on.io_update_trigger
- The HighResTimerE used to generate IO_Update pulses.config
- The frozen DDS configuration.Get a builder for serializing a Pounder DDS profile.
+pub struct ProfileBuilder<'a> { /* private fields */ }
A temporary builder for serializing and writing profiles.
+Update a number of channels with the provided configuration
+channels
- A list of channels to apply the configuration to.ftw
- If provided, indicates a frequency tuning word for the channels.pow
- If provided, indicates a phase offset word for the channels.acr
- If provided, indicates the amplitude control register for the channels. The
+24-bits of the ACR should be stored in the last 3 LSB.pub enum Channel {
+ In0 = 0,
+ Out0 = 1,
+ In1 = 2,
+ Out1 = 3,
+}
The numerical value (discriminant) of the Channel enum is the index in the attenuator shift +register as well as the attenuator latch enable signal index on the GPIO extender.
+pub enum Error {
+ Spi,
+ I2c,
+ Qspi(QspiError),
+ Bounds,
+ InvalidAddress,
+ InvalidChannel,
+ Adc,
+ InvalidState,
+}
pub enum GpioPin {
+Show 13 variants
Led4Green,
+ Led5Red,
+ Led6Green,
+ Led7Red,
+ Led8Green,
+ Led9Red,
+ AttLe0,
+ AttLe1,
+ AttLe2,
+ AttLe3,
+ AttRstN,
+ OscEnN,
+ ExtClkSel,
+}
pub enum Channel {
+ One,
+ Two,
+}
A HRTimer output channel.
+pub struct HighResTimerE { /* private fields */ }
The high resolution timer. Currently, only Timer E is supported.
+Construct a new high resolution timer for generating IO_update signals.
+Configure the timer to operate in single-shot mode.
+This will configure the timer to generate a single pulse on an output channel. The timer
+will only count up once and must be trigger()
’d after / configured.
The output will be asserted from set_offset
to set_offset
+ set_duration
in the count.
channel
- The timer output channel to configure.set_duration
- The duration that the output should be asserted for.set_offset
- The first time at which the output should be asserted.pub trait PowerMeasurementInterface {
+ // Required method
+ fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
+
+ // Provided method
+ fn measure_power(&mut self, channel: Channel) -> Result<f32, Error> { ... }
+}
Provide an interface to measure RF input power in dBm.
+pub struct ChannelState {
+ pub parameters: DdsChannelState,
+ pub attenuation: f32,
+}
parameters: DdsChannelState
§attenuation: f32
source
. Read morepub struct DdsChannelState {
+ pub phase_offset: f32,
+ pub frequency: f32,
+ pub amplitude: f32,
+ pub enabled: bool,
+}
phase_offset: f32
§frequency: f32
§amplitude: f32
§enabled: bool
source
. Read morepub struct DdsClockConfig {
+ pub multiplier: u8,
+ pub reference_clock: f32,
+ pub external_clock: bool,
+}
multiplier: u8
§reference_clock: f32
§external_clock: bool
source
. Read morepub struct InputChannelState {
+ pub attenuation: f32,
+ pub power: f32,
+ pub mixer: DdsChannelState,
+}
attenuation: f32
§power: f32
§mixer: DdsChannelState
source
. Read morepub struct OutputChannelState {
+ pub attenuation: f32,
+ pub channel: DdsChannelState,
+}
attenuation: f32
§channel: DdsChannelState
source
. Read morepub struct PounderDevices {
+ pub lm75: Lm75<I2c1Proxy, Lm75>,
+ /* private fields */
+}
A structure containing implementation for Pounder hardware.
+lm75: Lm75<I2c1Proxy, Lm75>
Construct and initialize pounder-specific hardware.
+Args:
+lm75
- The temperature sensor on Pounder.mcp23017
- The GPIO expander on Pounder.attenuator_spi
- A SPI interface to control digital attenuators.pwr0
- The ADC channel to measure the IN0 input power.pwr1
- The ADC channel to measure the IN1 input power.aux_adc0
- The ADC channel to measure the ADC0 auxiliary input.aux_adc1
- The ADC channel to measure the ADC1 auxiliary input.Sample one of the two auxiliary ADC channels associated with the respective RF input channel.
+Reset all of the attenuators to a power-on default state.
+Latch a configuration into a digital attenuator.
+Args:
+channel
- The attenuator channel to latch.Read the raw attenuation codes stored in the attenuator shift registers.
+Args:
+channels
- A 4 byte slice to be shifted into the
+attenuators and to contain the data shifted out.pub struct QspiInterface {
+ pub qspi: Qspi<QUADSPI>,
+ /* private fields */
+}
A structure for the QSPI interface for the DDS.
+qspi: Qspi<QUADSPI>
Configure the operations mode of the interface.
+Args:
+mode
- The newly desired operational mode.Write data over QSPI to the DDS.
+Args:
+addr
- The address to write over QSPI to the DDS.data
- The data to write.ADC sample timestamper using external Pounder reference clock.
+The pounder timestamper utilizes the pounder SYNC_CLK output as a fast external reference clock +for recording a timestamp for each of the ADC samples.
+To accomplish this, a timer peripheral is configured to be driven by an external clock input. +Due to the limitations of clock frequencies allowed by the timer peripheral, the SYNC_CLK input +is divided by 4. This clock then clocks the timer peripheral in a free-running mode with an ARR +(max count register value) configured to overflow once per ADC sample batch.
+Once the timer is configured, an input capture is configured to record the timer count +register. The input capture is configured to utilize an internal trigger for the input capture. +The internal trigger is selected such that when a sample is generated on ADC0, the input +capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the +batch size. This results in the input capture triggering identically to when the ADC samples +the last sample of the batch. That sample is then available for processing by the user.
+pub struct Timestamper { /* private fields */ }
Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
+Construct the pounder sample timestamper.
+timestamp_timer
- The timer peripheral used for capturing timestamps from.capture_channel
- The input capture channel for collecting timestamps.sampling_timer
- The stabilizer ADC sampling timer._clock_input
- The input pin for the external clock from Pounder.batch_size
- The number of samples in each batch.The new pounder timestamper in an operational state.
+Update the period of the underlying timestamp timer.
+pub fn setup(
+ core: CorePeripherals,
+ device: Peripherals,
+ clock: SystemTimer,
+ batch_size: usize,
+ sample_ticks: u32
+) -> (StabilizerDevices, Option<PounderDevices>)
Configure the stabilizer hardware for operation.
+Refer to design_parameters::TIMER_FREQUENCY to determine the frequency of the sampling timer.
+core
- The cortex-m peripherals.device
- The microcontroller peripherals to be configured.clock
- A SystemTimer
implementing Clock
.batch_size
- The size of each ADC/DAC batch.sample_ticks
- The number of timer ticks between each sample.(stabilizer, pounder) where stabilizer
is a StabilizerDevices
structure containing all
+stabilizer hardware interfaces in a disabled state. pounder
is an Option
containing
+Some(devices)
if pounder is detected, where devices
is a PounderDevices
structure
+containing all of the pounder hardware interfaces in a disabled state.
Stabilizer hardware configuration
+This file contains all of the hardware-specific configuration of Stabilizer.
+pub struct EemGpioDevices {
+ pub lvds4: EemDigitalInput0,
+ pub lvds5: EemDigitalInput1,
+ pub lvds6: EemDigitalOutput0,
+ pub lvds7: EemDigitalOutput1,
+}
The GPIO pins available on the EEM connector, if Pounder is not present.
+lvds4: EemDigitalInput0
§lvds5: EemDigitalInput1
§lvds6: EemDigitalOutput0
§lvds7: EemDigitalOutput1
pub struct NetStorage {
+ pub ip_addrs: [IpCidr; 1],
+ pub sockets: [SocketStorage<'static>; 7],
+ pub tcp_socket_storage: [TcpSocketStorage; 4],
+ pub udp_socket_storage: [UdpSocketStorage; 1],
+ pub dns_storage: [Option<DnsQuery>; 1],
+}
ip_addrs: [IpCidr; 1]
§sockets: [SocketStorage<'static>; 7]
§tcp_socket_storage: [TcpSocketStorage; 4]
§udp_socket_storage: [UdpSocketStorage; 1]
§dns_storage: [Option<DnsQuery>; 1]
pub struct NetworkDevices {
+ pub stack: NetworkStack,
+ pub phy: EthernetPhy,
+ pub mac_address: EthernetAddress,
+}
The available networking devices on Stabilizer.
+stack: NetworkStack
§phy: EthernetPhy
§mac_address: EthernetAddress
pub struct PounderDevices {
+ pub pounder: PounderDevices,
+ pub dds_output: DdsOutput,
+ pub timestamper: Timestamper,
+}
The available Pounder-specific hardware interfaces.
+pounder: PounderDevices
§dds_output: DdsOutput
§timestamper: Timestamper
pub struct StabilizerDevices {Show 14 fields
+ pub systick: Systick,
+ pub temperature_sensor: CpuTempSensor,
+ pub afes: (AFE0, AFE1),
+ pub adcs: (Adc0Input, Adc1Input),
+ pub dacs: (Dac0Output, Dac1Output),
+ pub timestamper: InputStamper,
+ pub adc_dac_timer: SamplingTimer,
+ pub timestamp_timer: TimestampTimer,
+ pub net: NetworkDevices,
+ pub digital_inputs: (DigitalInput0, DigitalInput1),
+ pub eem_gpio: EemGpioDevices,
+ pub usb_serial: SerialTerminal,
+ pub usb: UsbDevice,
+ pub metadata: &'static ApplicationMetadata,
+}
The available hardware interfaces on Stabilizer.
+systick: Systick
§temperature_sensor: CpuTempSensor
§afes: (AFE0, AFE1)
§adcs: (Adc0Input, Adc1Input)
§dacs: (Dac0Output, Dac1Output)
§timestamper: InputStamper
§adc_dac_timer: SamplingTimer
§timestamp_timer: TimestampTimer
§net: NetworkDevices
§digital_inputs: (DigitalInput0, DigitalInput1)
§eem_gpio: EemGpioDevices
§usb_serial: SerialTerminal
§usb: UsbDevice
§metadata: &'static ApplicationMetadata
pub struct TcpSocketStorage { /* private fields */ }
source
. Read morepub struct UdpSocketStorage { /* private fields */ }
source
. Read morepub enum AdcError {
+ InUse,
+}
Indicates that the ADC is already in use
+pub struct AdcChannel<'a, Adc, PIN> { /* private fields */ }
A single channel on an ADC peripheral.
+pub struct SharedAdc<Adc> { /* private fields */ }
An ADC peripheral that can provide ownership of individual channels for sharing between +drivers.
+Construct a new shared ADC driver.
+slope
- The slope of the ADC conversion transfer function.adc
- The ADC peripheral to share.Allocate an ADC channel for usage.
+pin
- The ADC input associated with the desired ADC channel. Often, this is a GPIO pin.An instantiated AdcChannel whose ownership can be transferred to other drivers.
+pub enum Error {
+ InvalidAmplitude,
+ InvalidSymmetry,
+ InvalidFrequency,
+}
Represents the errors that can occur when attempting to configure the signal generator.
+The provided amplitude is out-of-range.
+The provided symmetry is out of range.
+The provided frequency is out of range.
+pub enum Signal {
+ Cosine,
+ Square,
+ Triangle,
+ WhiteNoise,
+}
Types of signals that can be generated.
+pub struct BasicConfig {
+ pub signal: Signal,
+ pub frequency: f32,
+ pub symmetry: f32,
+ pub amplitude: f32,
+ pub phase: f32,
+}
Basic configuration for a generated signal.
+{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}
Where <signal>
may be any of Signal variants, frequency
specifies the signal frequency
+in Hertz, symmetry
specifies the normalized signal symmetry which ranges from 0 - 1.0, and
+amplitude
specifies the signal amplitude in Volts.
signal: Signal
The signal type that should be generated. See Signal variants.
+frequency: f32
The frequency of the generated signal in Hertz.
+symmetry: f32
The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal. +At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this +symmetry is the duty cycle.
+amplitude: f32
The amplitude of the output signal in volts.
+phase: f32
The phase of the output signal in turns.
+source
. Read moreindices
. Read morepub struct Config {
+ pub signal: Signal,
+ pub amplitude: i16,
+ pub phase_increment: [i32; 2],
+ pub phase_offset: i32,
+}
signal: Signal
The type of signal being generated
+amplitude: i16
The full-scale output code of the signal
+phase_increment: [i32; 2]
The frequency tuning word of the signal. Phase is incremented by this amount
+phase_offset: i32
The phase offset
+pub struct SignalGenerator { /* private fields */ }
Update waveform generation settings.
+Clear the phase accumulator.
+iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)pub enum InputFilter {
+ Div1N1 = 0,
+ Div1N8 = 3,
+}
Optional input capture preconditioning filter configurations.
+#[repr(u8)]pub enum Prescaler {
+ Div1 = 0,
+ Div2 = 1,
+ Div4 = 2,
+ Div8 = 3,
+}
Prescalers for externally-supplied reference clocks.
+pub enum SlaveMode {
+ Disabled = 0,
+ Trigger = 6,
+}
Optional slave operation modes of a timer.
+pub enum TriggerGenerator {
+ Reset = 0,
+ Enable = 1,
+ Update = 2,
+ ComparePulse = 3,
+ Ch1Compare = 4,
+ Ch2Compare = 5,
+ Ch3Compare = 6,
+ Ch4Compare = 7,
+}
The event that should generate an external trigger from the peripheral.
+pub enum TriggerSource {
+ Trigger0 = 0,
+ Trigger1 = 1,
+ Trigger2 = 2,
+ Trigger3 = 3,
+}
Selects the trigger source for the timer peripheral.
+The sampling timer is used for managing ADC sampling and external reference timestamping.
+pub struct PounderTimestampTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct SamplingTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct ShadowSamplingTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+pub struct TimestampTimer { /* private fields */ }
The timer used for managing ADC sampling.
+Get the timer update event.
+Get the period of the timer.
+Manually set the period of the timer.
+Configure the timer peripheral to generate a trigger based on the provided +source.
+Select a trigger source for the timer peripheral.
+#[repr(u8)]pub enum CaptureSource1 {
+ Ti1 = 1,
+ Ti2 = 2,
+ Trc = 3,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2 = 1,
+ Ti1 = 2,
+ Trc = 3,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3 = 1,
+ Ti4 = 2,
+ Trc = 3,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4 = 1,
+ Ti3 = 2,
+ Trc = 3,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1 = 1,
+ Ti2 = 2,
+ Trc = 3,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2 = 1,
+ Ti1 = 2,
+ Trc = 3,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3 = 1,
+ Ti4 = 2,
+ Trc = 3,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4 = 1,
+ Ti3 = 2,
+ Trc = 3,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1 = 1,
+ Ti2 = 2,
+ Trc = 3,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2 = 1,
+ Ti1 = 2,
+ Trc = 3,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3 = 1,
+ Ti4 = 2,
+ Trc = 3,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4 = 1,
+ Ti3 = 2,
+ Trc = 3,
+}
Capture/Compare 4 selection
+Value on reset: 0
+1: CC4 channel is configured as input, IC4 is mapped on TI4
+2: CC4 channel is configured as input, IC4 is mapped on TI3
+3: CC4 channel is configured as input, IC4 is mapped on TRC
+pub struct Channel1 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel1InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel2 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel2InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel3 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel3InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channel4 {}
A capture/compare channel of the timer.
+Allow the channel to generate DMA requests.
+Operate the channel as an output-compare.
+value
- The value to compare the sampling timer’s counter against.Operate the channel in input-capture mode.
+input
- The input source for the input capture event.pub struct Channel4InputCapture {}
A capture channel of the timer.
+Get the latest capture from the channel.
+Allow the channel to generate DMA requests.
+Check if an over-capture event has occurred.
+Configure the input capture input pre-filter.
+filter
- The desired input filter stage configuration. Defaults to disabled.pub struct Channels {
+ pub ch1: Channel1,
+ pub ch2: Channel2,
+ pub ch3: Channel3,
+ pub ch4: Channel4,
+}
The channels representing the timer.
+ch1: Channel1
§ch2: Channel2
§ch3: Channel3
§ch4: Channel4
pub struct UpdateEvent {}
#[repr(u8)]pub enum CaptureSource1 {
+ Ti1 = 1,
+ Ti2 = 2,
+ Trc = 3,
+}
Capture/Compare 1 selection
+Value on reset: 0
+1: CC1 channel is configured as input, IC1 is mapped on TI1
+2: CC1 channel is configured as input, IC1 is mapped on TI2
+3: CC1 channel is configured as input, IC1 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource2 {
+ Ti2 = 1,
+ Ti1 = 2,
+ Trc = 3,
+}
Capture/Compare 2 selection
+Value on reset: 0
+1: CC2 channel is configured as input, IC2 is mapped on TI2
+2: CC2 channel is configured as input, IC2 is mapped on TI1
+3: CC2 channel is configured as input, IC2 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource3 {
+ Ti3 = 1,
+ Ti4 = 2,
+ Trc = 3,
+}
Capture/compare 3 selection
+Value on reset: 0
+1: CC3 channel is configured as input, IC3 is mapped on TI3
+2: CC3 channel is configured as input, IC3 is mapped on TI4
+3: CC3 channel is configured as input, IC3 is mapped on TRC
+#[repr(u8)]pub enum CaptureSource4 {
+ Ti4 = 1,
+ Ti3 = 2,
+ Trc = 3,
+}
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>>>;
struct AFE0 { /* private fields */ }
pub type AFE1 = ProgrammableGainAmplifier<PD14<Output<PushPull>>, PD15<Output<PushPull>>>;
struct AFE1 { /* private fields */ }
pub type DigitalInput0 = PG9<Input>;
struct DigitalInput0 { /* private fields */ }
pub type DigitalInput1 = PC15<Input>;
struct DigitalInput1 { /* private fields */ }
pub type EemDigitalInput0 = PD1<Input>;
struct EemDigitalInput0 { /* private fields */ }
pub type EemDigitalInput1 = PD2<Input>;
struct EemDigitalInput1 { /* private fields */ }
pub type EemDigitalOutput0 = PD3<Output>;
struct EemDigitalOutput0 { /* private fields */ }
pub type EemDigitalOutput1 = PD4<Output>;
struct EemDigitalOutput1 { /* private fields */ }
pub type EthernetPhy = LAN8742A<EthernetMAC>;
struct EthernetPhy { /* private fields */ }
pub type I2c1 = I2c<I2C1>;
struct I2c1 { /* private fields */ }
pub type I2c1Proxy = I2cProxy<'static, AtomicCheckMutex<I2c1>>;
struct I2c1Proxy { /* private fields */ }
pub type NetworkManager = NetworkManager<'static, EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>, SystemTimer>;
struct NetworkManager { /* private fields */ }
pub type NetworkStack = NetworkStack<'static, EthernetDMA<TX_DESRING_CNT, RX_DESRING_CNT>, SystemTimer>;
struct NetworkStack { /* private fields */ }
pub type SerialTerminal = Runner<'static, SerialSettingsPlatform>;
struct SerialTerminal(/* private fields */);
pub type SystemTimer = MonoClock<u32, MONOTONIC_FREQUENCY>;
struct SystemTimer(/* private fields */);
pub type Systick = Systick<MONOTONIC_FREQUENCY>;
struct Systick { /* private fields */ }
pub type UsbBus = UsbBus<USB2>;
struct UsbBus { /* private fields */ }
#[repr(u8)]pub enum StreamFormat {
+ Unknown = 0,
+ AdcDacData = 1,
+ Fls = 2,
+}
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, id: &str) -> String<128>
Stabilizer network management module
+The stabilizer network architecture supports numerous layers to permit transmission of +telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data +streaming over raw UDP/TCP sockets. This module encompasses the main processing routines +related to Stabilizer networking operations.
+Task to process network hardware.
+The network processir is a small taks to regularly process incoming data over ethernet, handle +the ethernet PHY state, and reset the network as appropriate.
+pub struct NetworkProcessor {
+ pub stack: NetworkReference,
+ /* private fields */
+}
Processor for managing network hardware.
+stack: NetworkReference
Handle ethernet link connection status.
+This may take non-trivial amounts of time to communicate with the PHY. As such, this should +only be called as often as necessary (e.g. once per second or so).
+pub struct MqttStorage { /* private fields */ }
pub struct NetworkUsers<S, T, const Y: usize>where
+ for<'de> S: Default + JsonCoreSlash<'de, Y> + Clone,
+ T: Serialize,{
+ pub miniconf: MqttClient<'static, S, NetworkReference, SystemTimer, NamedBroker<NetworkReference>, Y>,
+ pub processor: NetworkProcessor,
+ pub telemetry: TelemetryClient<T>,
+ /* private fields */
+}
A structure of Stabilizer’s default network users.
+miniconf: MqttClient<'static, S, NetworkReference, SystemTimer, NamedBroker<NetworkReference>, Y>
§processor: NetworkProcessor
§telemetry: TelemetryClient<T>
Construct Stabilizer’s default network users.
+stack
- The network stack that will be used to share with all network users.phy
- The ethernet PHY connecting the network.clock
- A SystemTimer
implementing Clock
.app
- The name of the application.broker
- The domain name of the MQTT broker to use.id
- The MQTT client ID base to use.metadata
- The application metadataA new struct of network users.
+Enable live data streaming.
+format
- A unique u8 code indicating the format of the data.Direct the stream to the provided remote target.
+remote
- The destination for the streamed data.Update and process all of the network users state.
+An indication if any of the network users indicated a state change. +The SettingsChanged option contains the path of the settings that changed.
+Stabilizer Telemetry Capabilities
+Telemetry is reported regularly using an MQTT client. All telemetry is reported in SI units +using standard JSON format.
+In order to report ADC/DAC codes generated during the DSP routines, a telemetry buffer is +employed to track the latest codes. Converting these codes to SI units would result in +repetitive and unnecessary calculations within the DSP routine, slowing it down and limiting +sampling frequency. Instead, the raw codes are stored and the telemetry is generated as +required immediately before transmission. This ensures that any slower computation required +for unit conversion can be off-loaded to lower priority tasks.
+pub struct Telemetry {
+ pub adcs: [f32; 2],
+ pub dacs: [f32; 2],
+ pub digital_inputs: [bool; 2],
+ pub cpu_temp: f32,
+}
The telemetry structure is data that is ultimately reported as telemetry over MQTT.
+This structure should be generated on-demand by the buffer when required to minimize conversion +overhead.
+adcs: [f32; 2]
Most recent input voltage measurement.
+dacs: [f32; 2]
Most recent output voltage.
+digital_inputs: [bool; 2]
Most recent digital input assertion state.
+cpu_temp: f32
The CPU temperature in degrees Celsius.
+pub struct TelemetryBuffer {
+ pub adcs: [AdcCode; 2],
+ pub dacs: [DacCode; 2],
+ pub digital_inputs: [bool; 2],
+}
The telemetry buffer is used for storing sample values during execution.
+These values can be converted to SI units immediately before reporting to save processing time. +This allows for the DSP process to continually update the values without incurring significant +run-time overhead during conversion to SI units.
+adcs: [AdcCode; 2]
The latest input sample on ADC0/ADC1.
+dacs: [DacCode; 2]
The latest output code on DAC0/DAC1.
+digital_inputs: [bool; 2]
The latest digital input states during processing.
+Convert the telemetry buffer to finalized, SI-unit telemetry for reporting.
+afe0
- The current AFE configuration for channel 0.afe1
- The current AFE configuration for channel 1.cpu_temp
- The current CPU temperature.The finalized telemetry structure that can be serialized and reported.
+source
. Read morepub struct TelemetryClient<T: Serialize> { /* private fields */ }
The telemetry client for reporting telemetry data over MQTT.
+pub type NetworkReference = NetworkStackProxy<'static, NetworkStack>;
struct NetworkReference { /* private fields */ }
pub enum Error<F> {
+ Postcard(Error),
+ Flash(F),
+}
Stabilizer Settings Management
+Stabilizer supports two types of settings:
+Static device configuration settings are loaded and used only at device power-up. These include +things like the MQTT broker address and the MQTT identified. Conversely, the dynamic run-time +settings can be changed and take effect immediately during device operation.
+This settings management interface is currently targeted at the static device configuration +settings. Settings are persisted into the unused 1MB flash bank of Stabilizer for future +recall. They can be modified via the USB interface to facilitate device configuration.
+Settings are stored in flash using a key-value pair mapping, where the key
is the name of the
+entry in the settings structure. This has a number of benefits:
Settings
structure can have new entries added to it in the future without losing old
+settings values, as each entry of the Settings
struct is stored separately as its own
+key-value pair.Settings
can be used among multiple Stabilizer firmware versions that need the same
+settings valuespub struct SerialSettingsPlatform {
+ pub interface: BestEffortInterface<SerialPort<'static, UsbBus>>,
+ pub settings: Settings,
+ pub storage: Flash,
+ pub metadata: &'static ApplicationMetadata,
+}
interface: BestEffortInterface<SerialPort<'static, UsbBus>>
The interface to read/write data to/from serially (via text) to the user.
+settings: Settings
The Settings structure.
+storage: Flash
The storage mechanism used to persist settings to between boots.
+metadata: &'static ApplicationMetadata
Metadata associated with the application
+Settings
.Interface
.pub struct Settings {
+ pub broker: String<255>,
+ pub id: String<23>,
+ pub mac: EthernetAddress,
+}
broker: String<255>
§id: String<23>
§mac: EthernetAddress
indices
. Read morepub struct SettingsItem {
+ pub path: String<32>,
+ pub data: Vec<u8, 256>,
+}
path: String<32>
§data: Vec<u8, 256>
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`The filter configuration Config
contains the filter gains.
For the first-order lowpass this is a single element array [k]
with\nthe corner frequency in scaled Q31:\nk = pi*(1 << 31)*f0/fn
where\nf0
is the 3dB corner frequency and\nfn
is the Nyquist frequency.\nThe corner frequency is warped in the usual way.
For the second-order lowpass this is [k**2/(1 << 32), -k/q]
with q = 1/sqrt(2)
\nfor a Butterworth response.
In addition to the poles at the corner frequency the filters have zeros at Nyquist.
\nThe first-order lowpass works fine and accurate for any positive gain\n1 <= k <= (1 << 31) - 1
.\nThe second-order lowpass works and is accurate for\n1 << 16 <= k <= q*(1 << 31)
.
! Create a new MonoClock
using e.g. RTIC’s monotonics::now()
.\n!\n! Args:\n! * now: a closure that returns the current ticks\n!\n! ! // In your `app` `init()`, set up a `Monotonic` as usual, e.g.: ! use mono_clock::MonoClock, systick_monotonic::Systick; ! const HZ: u32 = 1_000; ! let sysclk = 400_000_000u32; ! let mono = Systick::<HZ>::new(c.core.SYST, sysclk); ! // Then build a `Clock` that is `Copy` and can be passed ! // around by value or reference: ! let clock = MonoClock::<u32, HZ>::new(|| monotonics::now()); !
Constructor
\nplatform
- The platform associated with the serial settings, providing the necessary\ncontext and API to manage device settings.line_buf
- A buffer used for maintaining the serial menu input line. It should be at\nleast as long as the longest user input.serialize_buf
- A buffer used for serializing and deserializing settings. This buffer\nneeds to be at least as big as the entire serialized settings structure.Get the current device settings.
\nGet the device communication interface
\naddress
and then reads enough bytes to fill buffer
in a\nsingle transaction Read moreConstruct a new manager for a shared network stack
\nstack
- The network stack that is being shared.Acquire a proxy to the shared network stack.
\nA proxy that can be used in place of the network stack. Note the requirements of\nconcurrency listed in the description of this file for usage.
\nConstruct a new network stack.
\nThis implementation only supports up to 16 usable sockets.
\nAny handles provided to this function must not be used after constructing the network\nstack.
\nThis implementation currently only supports IPv4.
\nstack
- The ethernet interface to construct the network stack from.clock
- A clock to use for determining network time.A [embedded_nal] compatible network stack.
\nSeed the TCP port randomizer.
\nseed
- A seed of random data to use for randomizing local TCP port selection.Poll the network stack for potential updates.
\nA boolean indicating if the network stack updated in any way.
\nForce-close all sockets.
\nHandle a disconnection of the physical interface.
\nAccess the underlying network interface.
\nMutably access the underlying network interface.
\nModification of the underlying network interface may unintentionally interfere with\noperation of this library (e.g. through reset, modification of IP addresses, etc.). Mutable\naccess to the interface should be done with care.
\nPublic functions for the LAN8742A
\nCreate LAN8742A instance from ETHMAC peripheral
\nReleases the ETHMAC peripheral
\nCreate and initialise a new I2C peripheral.
\nThe frequency of the I2C bus clock is specified by frequency
.
Panics if the ratio between frequency
and the i2c_ker_ck\nis out of bounds. The acceptable range is [4, 8192].
Panics if the frequency
is too fast. The maximum is 1MHz.
Returns a reference to the inner peripheral
\nReturns a mutable reference to the inner peripheral
\nStart listening for event
Stop listening for event
Clears interrupt flag for event
Releases the I2C peripheral
\nMaster controller methods
\nThese infallible methods are used to begin or end parts of\ntransactions, but do not read or write the data\nregisters. If you want to perform an entire transcation see the\nRead and Write\nimplementations.
\nIf a previous transcation is still in progress, then these\nmethods will block until that transcation is complete. A\nprevious transaction can still be “in progress” up to 50% of a\nbus cycle after a ACK/NACK event. Otherwise these methods return\nimmediately.
\nMaster read
\nPerform an I2C start and prepare to receive length
bytes.
Master: ST SAD+R ... (SP)\nSlave: ...
Master write
\nPerform an I2C start and prepare to send length
bytes.
Master: ST SAD+W ... (SP)\nSlave: ...
Master restart
\nPerforms an I2C restart following a write phase and prepare\nto receive length
bytes. The I2C peripheral is configured\nto provide an automatic stop.
Master: ... SR SAD+R ... (SP)\nSlave: ... ...
Master stop
\nGenerate a stop condition.
\n\nMaster: ... SP\nSlave: ...
Constructs a new USB peripheral driver.
\nReads from a ULPI register in an external ULPI PHY.
\nInterrupts are disabled for the duration of the function call.
\nPanics: if phy_type
is not PhyType::ExternalHighSpeed
.
fn read_usb_vid_pid<USB: UsbPeripheral>(bus: &UsbBus<USB>) -> (u16, u16) {\n let mut vid: u16 = bus.ulpi_read(0x00) as u16;\n vid |= (bus.ulpi_read(0x01) as u16) << 8;\n let mut pid: u16 = bus.ulpi_read(0x02) as u16;\n pid |= (bus.ulpi_read(0x03) as u16) << 8;\n (vid, pid)\n}
Writes to a ULPI register in an external ULPI PHY.
\nInterrupts are disabled for the duration of the function call.
\nPanics: if phy_type
is not PhyType::ExternalHighSpeed
.
poll
returns [PollResult::Reset
]. This method should\nreset the state of all endpoints and peripheral flags back to a state suitable for\nenumeration, as well as ensure that all endpoints previously allocated with alloc_ep are\ninitialized as specified.addr
.poll
returns [PollResult::Suspend
]. The device will\ncontinue be polled, and it shall return a value other than Suspend
from poll
when it no\nlonger detects the suspend condition.PollResult
] struct for more information.set_device_address
must be called before accepting the corresponding\ncontrol transfer, not after. Read moreProvide a new Monotonic
based on SysTick.
The sysclk
parameter is the speed at which SysTick runs at. This value should come from\nthe clock generation function of the used HAL.
Notice that the actual rate of the timer is a best approximation based on the given\nsysclk
and TIMER_HZ
.
false
if one is using the on_interrupt
\nmethod to perform housekeeping and need overflow interrupts to happen, such as when\nextending a 16 bit timer to 32/64 bits, even if there are no scheduled tasks.Gets a reference to the [UsbBus
] implementation used by this UsbDevice
. You can use this\nto call platform-specific methods on the UsbBus
.
While it is also possible to call the standard UsbBus
trait methods through this\nreference, this is not recommended as it can cause the device to misbehave.
Gets the current state of the device.
\nIn general class traffic is only possible in the Configured
state.
Gets whether host remote wakeup has been enabled by the host.
\nGets whether the device is currently self powered.
\nSets whether the device is currently self powered.
\nSimulates a disconnect from the USB bus, causing the host to reset and re-enumerate the\ndevice.
\nMostly useful for development. Calling this at the start of your program ensures that the\nhost re-enumerates your device after a new program has been flashed.
\nPolls the [UsbBus
] for new events and dispatches them to the provided classes. Returns\ntrue if one of the classes may have data available for reading or be ready for writing,\nfalse otherwise. This should be called periodically as often as possible for the best data\nrate, or preferably from an interrupt handler. Must be called at least once every 10\nmilliseconds while connected to the USB host to be USB compliant.
Note: The list of classes passed in must be the same classes in the same order for every\ncall while the device is configured, or the device may enumerate incorrectly or otherwise\nmisbehave. The easiest way to do this is to call the poll
method in only one place in your\ncode, as follows:
usb_dev.poll(&mut [&mut class1, &mut class2]);
Strictly speaking the list of classes is allowed to change between polls if the device has\nbeen reset, which is indicated by state
being equal to [UsbDeviceState::Default
].
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.
+ +