1#![cfg_attr(target_os = "none", no_std)]
2#![cfg_attr(target_os = "none", no_main)]
3
4use 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#[derive(Clone, Debug, Tree)]
126#[tree(meta(doc, typename))]
127pub struct Fls {
128 ext_clk: bool,
130
131 channel: [Channel; 2],
133
134 pll_bandwidth: f32,
137
138 pll_split: f32,
140
141 telemetry_period: f32,
143
144 #[tree(with=miniconf::leaf)]
146 stream: stream::Target,
147
148 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: W<i32>,
175
176 attenuation: f32,
179}
180
181#[derive(Clone, Debug, Tree)]
182struct Channel {
183 demodulate: Dds,
187
188 #[tree(with=miniconf::leaf)]
196 demodulate_acr: u24,
197
198 #[tree(with=miniconf::leaf)]
200 afe_gain: Gain,
201
202 modulate: Dds,
207
208 hold_en: bool,
210
211 dsp: fls::Channel,
213}
214
215impl Default for Channel {
216 fn default() -> Self {
217 const F_DEMOD: W<i32> = W(0x5200_0000);
220
221 Self {
222 demodulate: Dds {
223 frequency: F_DEMOD + W(0x20_0000), 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#[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 demod: Complex<i32>,
327 phase: [u32; 2],
329 delta_ftw: W<i32>,
332 aux_adc: u16,
334 mod_amp: u16,
335}
336
337#[derive(Default, Clone, Serialize)]
339struct ChannelTelemetry {
340 phase_raw: i64,
342 phase: statistics::ScaledStatistics,
344 power: statistics::ScaledStatistics,
346 aux_adc: f32,
348 mod_amp: f32,
350 holds: W<u32>,
353 slips: W<u32>,
356 blanks: W<u32>,
359 rf_power: f32,
361}
362
363#[derive(Default, Clone, Serialize)]
367pub struct CookedTelemetry {
368 pll_time: i64,
372 channel: [ChannelTelemetry; 2],
374 pounder_temp: f32,
376 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 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 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 #[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 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 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}