fls/
fls.rs

1#![cfg_attr(target_os = "none", no_std)]
2#![cfg_attr(target_os = "none", no_main)]
3
4//! Patent pending: DE102021112017A1
5//!
6//! # Algorithm description
7//!
8//! This application can be understood as a universal phase (frequency)
9//! signal processor. It determines the phase (we will drop frequency
10//! from now on as in a phase-aware system frequency is merely the
11//! difference between successive phases) of an RF input signal and
12//! emits an RF output signal with a phase that depends on the input
13//! phase. The transfer function between input and output phase is a
14//! sequence of various types of filters (analog RC, digital FIR, IIR,
15//! unwrapping, scaling, clipping) designed to implement either
16//! high-quality phase measurements or a certain constrained and
17//! somewhat exotic phase locked loop that is highly applicable
18//! to the task of stabilizing the arm length of an optical Michelson
19//! interferometer which in turn occurs when stabilizing the effective
20//! path length of an optical frequency transmission system.
21//!
22//! The sequence of processing steps is as follows. Analyzing it's
23//! application in the context of optical path length stabilization including
24//! laser sources, optical modulators, and photodetectors optical is left as
25//! an exercise for the user.
26//!
27//! ## PLL path
28//!
29//! * DDS locks its sysclk (500 MHz) to XO or external ref
30//! * DDS emits SYNC signal at sysclk/4
31//! * Prescaler 1/4 (in CPU)
32//! * Drives CPU timer counter
33//! * Counter is captured once per batch (based on CPU clock).
34//!   See [stabilizer::hardware::pounder::timestamp].
35//! * Digital PLL reconstructs SYNC frequency and phase (thus sysclk)
36//!   w.r.t. batch and sample frequency and phase.
37//!   This determines the relation of the CPU 8 MHz crystal (thus CPU
38//!   clock and timers) to the DDS clock (derived from an external reference
39//!   frequency or internal XCO). See [idsp::PLL].
40//!
41//! ## Signal path
42//!
43//! * RF signal enters Pounder at Pounder IN0
44//! * Adjustable attenuation `demod_att`.
45//! * 30 dB gain block
46//! * Mixing with DDS at `demod_freq`
47//! * RC lowpass and amplification to reject unwanted demodulation products and
48//!   harmonics
49//! * IF signal enters Stabilizer and is available at ADC0 for analog monitoring
50//! * 2x PGIA and AA filter on Stabilizer
51//! * ADC digitization at 1/1.28 µs interval
52//! * Data processing in batches of 8 samples
53//! * Digital mixing with the reconstructed sample phase (PLL path). See [idsp::Lockin].
54//! * Lowpass filtering with a second order (12 dB/octave)
55//!   IIR lowpass with an additional double zero at Nyquist. Adjustable corner frequency.
56//!   See [idsp::Lowpass]
57//! * Full rate baseband demodulated data (quadrature only) on DAC0
58//! * Lowpass filtering with a batch-size boxcar FIR filter (zeros at n/4 Nyquist)
59//! * Computation of signal power and phase.
60//! * Fractional rescaling (`phase_scale`) and unwrapping of the phase with 32 bit turn range.
61//! * Scaling and clamping.
62//! * Filtering by a second order (biquad) IIR filter (supporting e.g. II, I, P
63//!   action). See [idsp::iir].
64//! * Clamping, output offset, and anti-windup. See [idsp::iir].
65//! * Feedback onto a frequency offset of the modulation DDS at `mod_freq`
66//! * Additional feedback path from the phase before unwrapping onto the
67//!   modulation DDS phase offset with an adjustable gain `pow_gain`
68//! * Adjustable DDS output amplitude and blanking on digital input
69//! * Adjustable modulation attenuation `mod_att`
70//! * Modulation output at Pounder OUT0
71//!
72//! # Telemetry
73//! Data is regularly published via MQTT. See [CookedTelemetry].
74//!
75//! # Streaming
76//! Full-rate ADC and DAC data is available via configurable UDP data streaming.
77//! See [stream]. To view and analyze noise spectra the graphical application
78//! [`stabilizer-stream`](https://github.com/quartiq/stabilizer-stream) can be used.
79
80use core::num::Wrapping as W;
81
82use arbitrary_int::{u10, u24};
83use core::sync::atomic::{Ordering, fence};
84use dsp_fixedpoint::P32;
85use dsp_process::{Process, SplitProcess};
86use idsp::{Complex, PLL, PLLState, Unwrapper};
87use miniconf::Tree;
88use serde::Serialize;
89
90use ad9959::Acr;
91use platform::{AppSettings, NetSettings};
92use stabilizer::{convert::Gain, fls, statistics};
93
94#[derive(Clone, Debug, Tree, Default)]
95#[tree(meta(doc, typename))]
96pub struct Settings {
97    fls: Fls,
98    net: NetSettings,
99}
100
101impl AppSettings for Settings {
102    fn new(net: NetSettings) -> Self {
103        Self {
104            net,
105            fls: Default::default(),
106        }
107    }
108
109    fn net(&self) -> &NetSettings {
110        &self.net
111    }
112}
113
114impl serial_settings::Settings for Settings {
115    fn reset(&mut self) {
116        *self = Self {
117            fls: Default::default(),
118            net: NetSettings::new(self.net.mac),
119        };
120    }
121}
122
123/// Settings structure for the application.
124/// All fields in this structure are available through MQTT and can be configured at runtime.
125#[derive(Clone, Debug, Tree)]
126#[tree(meta(doc, typename))]
127pub struct Fls {
128    /// External 100 MHz reference
129    ext_clk: bool,
130
131    /// Channel-specific settings.
132    channel: [Channel; 2],
133
134    /// Lockin demodulation oscillator PLL crossover in Hz.
135    /// This PLL reconstructs the DDS SYNC clock output on the CPU clock timescale.
136    pll_bandwidth: f32,
137
138    /// PLL lead-lag pole-zero ratio
139    pll_split: f32,
140
141    /// Telemetry output period in seconds.
142    telemetry_period: f32,
143
144    /// Specifies the target for data streaming.
145    #[tree(with=miniconf::leaf)]
146    stream: stream::Target,
147
148    /// Activate settings
149    ///
150    /// If `true` each settings change immediately results in activation.
151    /// If `false`, activation is suppressed.
152    /// Use this to synchronize changes.
153    activate: bool,
154}
155
156impl Default for Fls {
157    fn default() -> Self {
158        Self {
159            ext_clk: false,
160            channel: Default::default(),
161            pll_bandwidth: 1e-4 / 10.24e-6,
162            pll_split: 4.0,
163            telemetry_period: 10.,
164            stream: Default::default(),
165            activate: true,
166        }
167    }
168}
169
170#[derive(Clone, Debug, miniconf::Tree)]
171struct Dds {
172    /// Frequency tuning word (i32)
173    /// The DDS sample clock is nominally 500 MHz.
174    frequency: W<i32>,
175
176    /// Attenuation (dB)
177    /// Range [0, 31.5]
178    attenuation: f32,
179}
180
181#[derive(Clone, Debug, Tree)]
182struct Channel {
183    /// Input (demodulation) DDS settings
184    ///
185    /// The demodulation frequency must include the second LO offset
186    demodulate: Dds,
187
188    /// Demodulation LO DDS amplitude control register.
189    ///
190    /// # Value
191    /// AD9959 amplitude control register (24 bits, see datasheet)
192    ///
193    /// # Default
194    /// 0 for full scale amplitude and multiplier disable
195    #[tree(with=miniconf::leaf)]
196    demodulate_acr: u24,
197
198    /// AFE gain
199    #[tree(with=miniconf::leaf)]
200    afe_gain: Gain,
201
202    /// Output (modulation) DDS settings
203    ///
204    /// Feedback to stabilize the RF input phase is applied to the RF output
205    /// on top of the output frequency
206    modulate: Dds,
207
208    /// Allow digital input channel to clear phase/frequency IIR and hold amplitude IIR
209    hold_en: bool,
210
211    /// Channel DSP components
212    dsp: fls::Channel,
213}
214
215impl Default for Channel {
216    fn default() -> Self {
217        // Default modulation/demodulation frequency for characterization.
218        // High CTZ has fewest DDS phase truncation spurs. Near 160 MHz.
219        const F_DEMOD: W<i32> = W(0x5200_0000);
220
221        Self {
222            demodulate: Dds {
223                frequency: F_DEMOD + W(0x20_0000), // LSB
224                attenuation: 31.5,
225            },
226            modulate: Dds {
227                frequency: F_DEMOD,
228                attenuation: 6.0,
229            },
230            demodulate_acr: u24::new(0),
231            hold_en: false,
232            afe_gain: Gain::G5,
233            dsp: Default::default(),
234        }
235    }
236}
237
238#[derive(Debug, Clone)]
239struct ChannelConfig {
240    dsp: fls::ChannelConfig,
241    hold_en: bool,
242    modulate_frequency: W<i32>,
243}
244
245#[derive(Debug, Clone)]
246pub struct Config {
247    channel: [ChannelConfig; 2],
248    pll: PLL,
249}
250
251#[derive(Default, Debug, Clone)]
252pub struct ChannelState {
253    dsp: fls::ChannelState,
254    phase: statistics::State,
255    power: statistics::State,
256}
257
258impl ChannelState {
259    fn update(&mut self) {
260        self.phase.update(self.dsp.phase.xy.x0());
261        self.power.update(self.dsp.power.inner as _);
262    }
263
264    fn stream(&self) -> StreamChannel {
265        StreamChannel {
266            demod: self.dsp.demod,
267            phase: bytemuck::cast(self.dsp.unwrap.y),
268            delta_ftw: W(self.dsp.phase.xy.y0()),
269            aux_adc: self.dsp.amplitude.x0() as _,
270            mod_amp: (self.dsp.amplitude.y0() >> 21) as _,
271        }
272    }
273}
274
275#[derive(Default, Debug, Clone)]
276pub struct State {
277    channel: [ChannelState; 2],
278    pll: PLLState,
279    pll_time: Unwrapper<i64>,
280}
281
282impl State {
283    fn update(&mut self) {
284        self.pll_time.process(self.pll.phase().0);
285        for c in self.channel.iter_mut() {
286            c.update();
287        }
288    }
289
290    fn stream(&self) -> Stream {
291        Stream {
292            pll: self.pll.phase(),
293            channel: self.channel.each_ref().map(|s| s.stream()),
294        }
295    }
296}
297
298impl Fls {
299    fn build(&self) -> Config {
300        Config {
301            channel: self.channel.each_ref().map(|c| ChannelConfig {
302                dsp: c.dsp.build(),
303                hold_en: c.hold_en,
304                modulate_frequency: c.modulate.frequency,
305            }),
306            pll: PLL::from_bandwidth(
307                self.pll_bandwidth * 10.24e-6,
308                self.pll_split,
309            ),
310        }
311    }
312}
313
314/// Stream data format.
315#[derive(Clone, Copy, Debug, Default, bytemuck::Zeroable, bytemuck::Pod)]
316#[repr(C)]
317struct Stream {
318    pll: W<i32>,
319    channel: [StreamChannel; 2],
320}
321
322#[derive(Clone, Copy, Debug, Default, bytemuck::Zeroable, bytemuck::Pod)]
323#[repr(C)]
324struct StreamChannel {
325    /// Demodulated signal.
326    demod: Complex<i32>,
327    /// Current raw phase including wraps and pll correction.
328    phase: [u32; 2],
329    /// Current frequency tuning word added to the configured modulation
330    /// offset `mod_freq`.
331    delta_ftw: W<i32>,
332    /// AUX ADC sample
333    aux_adc: u16,
334    mod_amp: u16,
335}
336
337/// Channel Telemetry, all undersampled and not AA filtered
338#[derive(Default, Clone, Serialize)]
339struct ChannelTelemetry {
340    /// Current phase.
341    phase_raw: i64,
342    /// Demodulated phase statistics in turns
343    phase: statistics::ScaledStatistics,
344    /// Demodulated power (amplitude squared) statistics re full scale
345    power: statistics::ScaledStatistics,
346    /// Auxiliary front panel ADC input values, V
347    aux_adc: f32,
348    /// Modulation amplitude re full scale
349    mod_amp: f32,
350    /// Number of cycles where digital input held amplitude and
351    /// cleared the frequency servo.
352    holds: W<u32>,
353    /// Number of potential phase slips where the absolute
354    /// phase difference between successive samples is larger than π/2.
355    slips: W<u32>,
356    /// Counter for the number of samples with low power where the
357    /// frequency servo was cleared.
358    blanks: W<u32>,
359    /// Total RF power in dBm as reported by the RF detector.
360    rf_power: f32,
361}
362
363/// Telemetry structure.
364/// This structure is published via MQTT at the `telemetry_interval` configured in
365/// [Settings].
366#[derive(Default, Clone, Serialize)]
367pub struct CookedTelemetry {
368    /// PLL time
369    /// DDS PLL time as seen by CPU (sample) clock. Units of `1ns / (1 <<11)`.
370    /// Settles increments of approximately `0x140_0000`.
371    pll_time: i64,
372    /// Channel telemetry, mostly "stateful" and undersampled
373    channel: [ChannelTelemetry; 2],
374    /// Pounder board temperature
375    pounder_temp: f32,
376    /// CPU temperature,
377    cpu_temp: f32,
378}
379
380#[cfg(not(target_os = "none"))]
381fn main() {
382    use miniconf::{json::to_json_value, json_schema::TreeJsonSchema};
383    let s = Settings::default();
384    println!(
385        "{}",
386        serde_json::to_string_pretty(&to_json_value(&s).unwrap()).unwrap()
387    );
388    let mut schema = TreeJsonSchema::new(Some(&s)).unwrap();
389    schema
390        .root
391        .insert("title".to_string(), "Stabilizer fls".into());
392    println!("{}", serde_json::to_string_pretty(&schema.root).unwrap());
393}
394
395#[cfg(target_os = "none")]
396#[cfg_attr(target_os = "none", rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, LTDC, SDMMC]))]
397mod app {
398    use fugit::ExtU32 as _;
399    use rtic_monotonics::Monotonic;
400
401    use stabilizer::hardware::{
402        self, DigitalInput0, DigitalInput1, Pgia, SerialTerminal, SystemTimer,
403        Systick, UsbDevice,
404        adc::{Adc0Input, Adc1Input},
405        cpu_temp_sensor::CpuTempSensor,
406        dac::{Dac0Output, Dac1Output},
407        hal,
408        net::{NetworkState, NetworkUsers},
409        pounder::{
410            Channel, PounderDevices, dds_output::DdsOutput,
411            timestamp::Timestamper,
412        },
413        timers::SamplingTimer,
414    };
415
416    use stream::FrameGenerator;
417
418    use super::*;
419
420    #[shared]
421    struct Shared {
422        usb: UsbDevice,
423        network: NetworkUsers<Fls>,
424        settings: Settings,
425        config: Config,
426        state: State,
427        dds_output: DdsOutput,
428        pounder: PounderDevices,
429    }
430
431    #[local]
432    struct Local {
433        usb_terminal: SerialTerminal<Settings>,
434        sampling_timer: SamplingTimer,
435        digital_inputs: (DigitalInput0, DigitalInput1),
436        adcs: (Adc0Input, Adc1Input),
437        dacs: (Dac0Output, Dac1Output),
438        generator: FrameGenerator,
439        timestamper: Timestamper,
440        afes: [Pgia; 2],
441        cpu_temp_sensor: CpuTempSensor,
442    }
443
444    #[init]
445    fn init(c: init::Context) -> (Shared, Local) {
446        let clock = SystemTimer::new(|| Systick::now().ticks());
447
448        // Configure the microcontroller
449        let (mut carrier, mezzanine, _eem) = hardware::setup::setup::<Settings>(
450            c.core,
451            c.device,
452            clock,
453            fls::BATCH_SIZE,
454            1 << fls::SAMPLE_TICKS_E,
455        );
456
457        let mut network = NetworkUsers::new(
458            carrier.network_devices.stack,
459            carrier.network_devices.phy,
460            clock,
461            env!("CARGO_BIN_NAME"),
462            &carrier.settings.net,
463            carrier.metadata,
464        );
465
466        let generator = network.configure_streaming(stream::Format::Fls2);
467
468        let hardware::setup::Mezzanine::Pounder(mut pounder) = mezzanine else {
469            panic!("Missing Pounder Mezzanine");
470        };
471        pounder.timestamper.start();
472
473        // Enable ADC/DAC events
474        carrier.adcs.0.start();
475        carrier.adcs.1.start();
476        carrier.dacs.0.start();
477        carrier.dacs.1.start();
478
479        let shared = Shared {
480            usb: carrier.usb,
481            network,
482            config: carrier.settings.fls.build(),
483            settings: carrier.settings,
484            dds_output: pounder.dds_output,
485            pounder: pounder.pounder,
486            state: Default::default(),
487        };
488
489        let local = Local {
490            usb_terminal: carrier.usb_serial,
491            sampling_timer: carrier.sampling_timer,
492            digital_inputs: carrier.digital_inputs,
493            adcs: carrier.adcs,
494            dacs: carrier.dacs,
495            generator,
496            timestamper: pounder.timestamper,
497            afes: carrier.afes,
498            cpu_temp_sensor: carrier.temperature_sensor,
499        };
500
501        settings_update::spawn().unwrap();
502        telemetry::spawn().unwrap();
503        aux_adc::spawn().unwrap();
504        usb::spawn().unwrap();
505        ethernet_link::spawn().unwrap();
506        start::spawn().unwrap();
507
508        (shared, local)
509    }
510
511    #[task(priority = 1, local = [sampling_timer])]
512    async fn start(c: start::Context) {
513        Systick::delay(200.millis()).await;
514        c.local.sampling_timer.start();
515    }
516
517    /// Main DSP processing routine.
518    ///
519    /// See `dual-iir` for general notes on processing time and timing.
520    ///
521    /// This is an implementation of fiber length stabilization using super-heterodyne
522    /// (pounder + lockin) and digital feedback to a DDS.
523    #[task(binds = DMA1_STR4, local=[timestamper, adcs, dacs, generator, digital_inputs], shared = [config, state, dds_output], priority = 3)]
524    #[unsafe(link_section = ".itcm.process")]
525    fn process(c: process::Context) {
526        let process::LocalResources {
527            adcs: (adc0, adc1),
528            dacs: (dac0, dac1),
529            digital_inputs,
530            timestamper,
531            generator,
532            ..
533        } = c.local;
534
535        (c.shared.state, c.shared.config, c.shared.dds_output).lock(
536            |state, config, dds_output| {
537                (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
538                    fence(Ordering::SeqCst);
539                    config.channel[0].dsp.demodulate::<{ fls::BATCH_SIZE }>(
540                        &mut state.channel[0].dsp,
541                        (**adc0).try_into().unwrap(),
542                        (*dac0).try_into().unwrap(),
543                    );
544                    config.channel[1].dsp.demodulate::<{ fls::BATCH_SIZE }>(
545                        &mut state.channel[1].dsp,
546                        (**adc1).try_into().unwrap(),
547                        (*dac1).try_into().unwrap(),
548                    );
549                    fence(Ordering::SeqCst);
550                });
551
552                // A counter running at a fourth of the DDS SYNC interval is captured by
553                // the overflow of a timer synchronized to the sampling timer (locked to the
554                // CPU clock and the other CPU timer clocks).
555                // Captured timestamps are about 0x140 counts apart between batches
556                // for default parameters.
557                // They determine the phase and period of the DDS clock (driving the counter)
558                // in terms of the CPU clock (driving the capture).
559                // Discard double captures (overcaptures) and extrapolate.
560                // Extrapolate on no capture (undercapture).
561                let t = if let Ok(Some(t)) = timestamper.latest_timestamp() {
562                    W((t as i32) << 16)
563                } else {
564                    log::warn!("timestamp under/over");
565                    state.pll.clamp.x0 - state.pll.frequency()
566                };
567                let phase = -config.pll.process(&mut state.pll, t);
568
569                let mut builder = dds_output.builder();
570                let hold =
571                    [digital_inputs.0.is_high(), digital_inputs.1.is_high()];
572                for ((idx, hold), (config, state)) in
573                    [Channel::Out0, Channel::Out1].into_iter().zip(hold).zip(
574                        config.channel.iter().zip(state.channel.iter_mut()),
575                    )
576                {
577                    state.dsp.hold = hold && config.hold_en;
578                    config.dsp.update(&mut state.dsp, phase);
579                    builder.push(
580                        idx.into(),
581                        Some(
582                            config.modulate_frequency
583                                + W(state.dsp.phase.xy.y0()),
584                        ),
585                        None,
586                        Some(Acr::DEFAULT.with_multiplier(true).with_asf(
587                            u10::new((state.dsp.amplitude.y0() >> 21) as _),
588                        )),
589                    );
590                }
591                dds_output.write(builder);
592                state.update();
593                generator.add(|buf| {
594                    const N: usize = core::mem::size_of::<Stream>();
595                    buf[..N].copy_from_slice(bytemuck::cast_slice(
596                        bytemuck::bytes_of(&state.stream()),
597                    ));
598                    N
599                });
600            },
601        );
602    }
603
604    #[idle(shared=[network, usb, settings])]
605    fn idle(mut c: idle::Context) -> ! {
606        loop {
607            match (&mut c.shared.network, &mut c.shared.settings)
608                .lock(|net, settings| net.update(&mut settings.fls))
609            {
610                NetworkState::SettingsChanged => {
611                    settings_update::spawn().unwrap()
612                }
613                NetworkState::Updated => {}
614                NetworkState::NoChange => {
615                    // We can't sleep if USB is not in suspend.
616                    if c.shared.usb.lock(|usb| {
617                        usb.state()
618                            == usb_device::device::UsbDeviceState::Suspend
619                    }) {
620                        cortex_m::asm::wfi();
621                    }
622                }
623            }
624        }
625    }
626
627    #[task(priority = 1, shared=[network, settings, dds_output, pounder, config], local=[afes])]
628    async fn settings_update(mut c: settings_update::Context) {
629        c.shared.settings.lock(|settings| {
630            c.local.afes[0].set_gain(settings.fls.channel[0].afe_gain);
631            c.local.afes[1].set_gain(settings.fls.channel[1].afe_gain);
632
633            c.shared.pounder.lock(|p| {
634                for (ch, att) in [
635                    (
636                        Channel::In0,
637                        settings.fls.channel[0].demodulate.attenuation,
638                    ),
639                    (
640                        Channel::Out0,
641                        settings.fls.channel[0].modulate.attenuation,
642                    ),
643                    (
644                        Channel::In1,
645                        settings.fls.channel[1].demodulate.attenuation,
646                    ),
647                    (
648                        Channel::Out1,
649                        settings.fls.channel[1].modulate.attenuation,
650                    ),
651                ] {
652                    if p.set_attenuation(ch, att).is_err() {
653                        log::warn!("invalid attenuation");
654                    }
655                }
656                p.set_ext_clk(settings.fls.ext_clk).unwrap();
657            });
658            c.shared.dds_output.lock(|dds_output| {
659                let mut builder = dds_output.builder();
660                builder.push(
661                    Channel::In0.into(),
662                    Some(settings.fls.channel[0].demodulate.frequency),
663                    None,
664                    Some(Acr::new_with_raw_value(
665                        settings.fls.channel[0].demodulate_acr,
666                    )),
667                );
668                builder.push(
669                    Channel::In1.into(),
670                    Some(settings.fls.channel[1].demodulate.frequency),
671                    None,
672                    Some(Acr::new_with_raw_value(
673                        settings.fls.channel[1].demodulate_acr,
674                    )),
675                );
676                dds_output.write(builder);
677            });
678            c.shared
679                .network
680                .lock(|net| net.direct_stream(settings.fls.stream));
681
682            if settings.fls.activate {
683                let cfg = settings.fls.build();
684                c.shared.config.lock(|config| *config = cfg);
685            }
686        });
687    }
688
689    #[task(priority = 1, shared=[pounder, config, state])]
690    async fn aux_adc(mut c: aux_adc::Context) -> ! {
691        loop {
692            let aux_adc::SharedResources {
693                config,
694                state,
695                pounder,
696                ..
697            } = &mut c.shared;
698            let x = pounder.lock(|p| {
699                [
700                    p.sample_aux_adc_raw(Channel::In0).unwrap(),
701                    p.sample_aux_adc_raw(Channel::In1).unwrap(),
702                ]
703            });
704            (config, state).lock(|c, s| {
705                c.channel[0].dsp.power(&mut s.channel[0].dsp, x[0]);
706                c.channel[1].dsp.power(&mut s.channel[1].dsp, x[1]);
707            });
708            Systick::delay(10.millis()).await;
709        }
710    }
711
712    #[task(priority = 1, local=[cpu_temp_sensor], shared=[network, settings, state, pounder])]
713    async fn telemetry(mut c: telemetry::Context) -> ! {
714        loop {
715            let (pll_time, channel) = c.shared.state.lock(|s| {
716                (
717                    s.pll_time.y,
718                    s.channel.each_mut().map(|s| {
719                        (
720                            core::mem::take(&mut s.phase),
721                            core::mem::take(&mut s.power),
722                            ChannelTelemetry {
723                                phase_raw: s.dsp.unwrap.y,
724                                aux_adc: s.dsp.amplitude.x0() as f32
725                                    * fls::AMPLITUDE_UNITS.x,
726                                mod_amp: s.dsp.amplitude.y0() as f32
727                                    * fls::AMPLITUDE_UNITS.y,
728                                holds: s.dsp.holds,
729                                slips: s.dsp.slips,
730                                blanks: s.dsp.blanks,
731                                ..Default::default()
732                            },
733                        )
734                    }),
735                )
736            });
737            let mut channel = channel.map(|(phase, power, mut channel)| {
738                channel.phase = phase.get_scaled(fls::PHASE_UNITS.x);
739                channel.power = power.get_scaled(P32::<28>::DELTA);
740                channel
741            });
742            let cpu_temp = c.local.cpu_temp_sensor.get_temperature().unwrap();
743            let pounder_temp = c.shared.pounder.lock(|p| {
744                channel[0].rf_power = p.measure_power(Channel::In0).unwrap();
745                channel[1].rf_power = p.measure_power(Channel::In0).unwrap();
746                p.temperature().unwrap()
747            });
748
749            c.shared.network.lock(|net| {
750                net.telemetry.publish_telemetry(
751                    "/telemetry",
752                    &CookedTelemetry {
753                        pll_time,
754                        channel,
755                        pounder_temp,
756                        cpu_temp,
757                    },
758                )
759            });
760
761            let telemetry_period =
762                c.shared.settings.lock(|s| s.fls.telemetry_period);
763            Systick::delay(((telemetry_period * 1e3) as u32).millis()).await;
764        }
765    }
766
767    #[task(priority = 1, shared=[usb, settings], local=[usb_terminal])]
768    async fn usb(mut c: usb::Context) -> ! {
769        loop {
770            if c.shared.usb.lock(|usb| {
771                usb.poll(&mut [c
772                    .local
773                    .usb_terminal
774                    .interface_mut()
775                    .inner_mut()])
776            }) && c.shared.settings.lock(|settings| {
777                c.local.usb_terminal.poll(settings).unwrap_or_else(|_| {
778                    log::warn!("USB error");
779                    false
780                })
781            }) {
782                settings_update::spawn().unwrap();
783            }
784            Systick::delay(10.millis()).await;
785        }
786    }
787
788    #[task(priority = 1, shared=[network])]
789    async fn ethernet_link(mut c: ethernet_link::Context) -> ! {
790        loop {
791            c.shared.network.lock(|net| net.processor.handle_link());
792            Systick::delay(1.secs()).await;
793        }
794    }
795
796    #[task(binds = ETH, priority = 1)]
797    fn eth(_: eth::Context) {
798        unsafe { hal::ethernet::interrupt_handler() }
799    }
800}