stabilizer/hardware/
setup.rs

1//! Stabilizer hardware configuration
2//!
3//! This file contains all of the hardware-specific configuration of Stabilizer.
4use bit_field::BitField;
5use core::cell::RefCell;
6use core::mem::MaybeUninit;
7use core::sync::atomic::{self, AtomicBool, Ordering};
8use core::{fmt::Write, ptr, slice};
9use embedded_hal_compat::{markers::ForwardOutputPin, Forward, ForwardCompat};
10use heapless::String;
11use stm32h7xx_hal::{
12    self as hal,
13    ethernet::{self, PHY},
14    gpio::Speed,
15    prelude::*,
16};
17
18use smoltcp_nal::smoltcp;
19
20use crate::hardware::delay::AsmDelay;
21use crate::settings::{AppSettings, NetSettings};
22
23use super::{
24    adc, afe, cpu_temp_sensor::CpuTempSensor, dac, delay, design_parameters,
25    eeprom, input_stamper::InputStamper, metadata::ApplicationMetadata,
26    platform, pounder, pounder::dds_output::DdsOutput, shared_adc::SharedAdc,
27    timers, DigitalInput0, DigitalInput1, Eem, EthernetPhy, Gpio,
28    HardwareVersion, NetworkStack, SerialTerminal, SystemTimer, Systick,
29    UsbDevice, AFE0, AFE1,
30};
31
32const NUM_TCP_SOCKETS: usize = 4;
33const NUM_UDP_SOCKETS: usize = 1;
34const NUM_SOCKETS: usize = NUM_UDP_SOCKETS + NUM_TCP_SOCKETS;
35
36pub struct NetStorage {
37    pub ip_addrs: [smoltcp::wire::IpCidr; 1],
38
39    // Note: There is an additional socket set item required for the DHCP and DNS sockets
40    // respectively.
41    pub sockets: [smoltcp::iface::SocketStorage<'static>; NUM_SOCKETS + 2],
42    pub tcp_socket_storage: [TcpSocketStorage; NUM_TCP_SOCKETS],
43    pub udp_socket_storage: [UdpSocketStorage; NUM_UDP_SOCKETS],
44    pub dns_storage: [Option<smoltcp::socket::dns::DnsQuery>; 1],
45}
46
47#[derive(Copy, Clone)]
48pub struct UdpSocketStorage {
49    rx_storage: [u8; 1024],
50    tx_storage: [u8; 2048],
51    tx_metadata: [smoltcp::storage::PacketMetadata<
52        smoltcp::socket::udp::UdpMetadata,
53    >; 10],
54    rx_metadata: [smoltcp::storage::PacketMetadata<
55        smoltcp::socket::udp::UdpMetadata,
56    >; 10],
57}
58
59impl UdpSocketStorage {
60    const fn new() -> Self {
61        Self {
62            rx_storage: [0; 1024],
63            tx_storage: [0; 2048],
64            tx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
65            rx_metadata: [smoltcp::storage::PacketMetadata::EMPTY; 10],
66        }
67    }
68}
69
70#[derive(Copy, Clone)]
71pub struct TcpSocketStorage {
72    rx_storage: [u8; 1024],
73    tx_storage: [u8; 1024],
74}
75
76impl TcpSocketStorage {
77    const fn new() -> Self {
78        Self {
79            rx_storage: [0; 1024],
80            tx_storage: [0; 1024],
81        }
82    }
83}
84
85impl Default for NetStorage {
86    fn default() -> Self {
87        NetStorage {
88            // Placeholder for the real IP address, which is initialized at runtime.
89            ip_addrs: [smoltcp::wire::IpCidr::Ipv6(
90                smoltcp::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX,
91            )],
92            sockets: [smoltcp::iface::SocketStorage::EMPTY; NUM_SOCKETS + 2],
93            tcp_socket_storage: [TcpSocketStorage::new(); NUM_TCP_SOCKETS],
94            udp_socket_storage: [UdpSocketStorage::new(); NUM_UDP_SOCKETS],
95            dns_storage: [None; 1],
96        }
97    }
98}
99
100/// The available networking devices on Stabilizer.
101pub struct NetworkDevices {
102    pub stack: NetworkStack,
103    pub phy: EthernetPhy,
104    pub mac_address: smoltcp::wire::EthernetAddress,
105}
106
107/// The available hardware interfaces on Stabilizer.
108pub struct StabilizerDevices<
109    C: serial_settings::Settings + 'static,
110    const Y: usize,
111> {
112    pub temperature_sensor: CpuTempSensor,
113    pub afes: (AFE0, AFE1),
114    pub adcs: (adc::Adc0Input, adc::Adc1Input),
115    pub dacs: (dac::Dac0Output, dac::Dac1Output),
116    pub timestamper: InputStamper,
117    pub adc_dac_timer: timers::SamplingTimer,
118    pub timestamp_timer: timers::TimestampTimer,
119    pub net: NetworkDevices,
120    pub digital_inputs: (DigitalInput0, DigitalInput1),
121    pub eem: Eem,
122    pub usb_serial: SerialTerminal<C, Y>,
123    pub usb: UsbDevice,
124    pub metadata: &'static ApplicationMetadata,
125    pub settings: C,
126}
127
128/// The available Pounder-specific hardware interfaces.
129pub struct PounderDevices {
130    pub pounder: pounder::PounderDevices,
131    pub dds_output: DdsOutput,
132
133    #[cfg(not(feature = "pounder_v1_0"))]
134    pub timestamper: pounder::timestamp::Timestamper,
135}
136
137#[link_section = ".sram3.eth"]
138/// Static storage for the ethernet DMA descriptor ring.
139static mut DES_RING: MaybeUninit<
140    ethernet::DesRing<{ super::TX_DESRING_CNT }, { super::RX_DESRING_CNT }>,
141> = MaybeUninit::uninit();
142
143/// Setup ITCM and load its code from flash.
144///
145/// For portability and maintainability this is implemented in Rust.
146/// Since this is implemented in Rust the compiler may assume that bss and data are set
147/// up already. There is no easy way to ensure this implementation will never need bss
148/// or data. Hence we can't safely run this as the cortex-m-rt `pre_init` hook before
149/// bss/data is setup.
150///
151/// Calling (through IRQ or directly) any code in ITCM before having called
152/// this method is undefined.
153fn load_itcm() {
154    extern "C" {
155        static mut __sitcm: u32;
156        static mut __eitcm: u32;
157        static mut __siitcm: u32;
158    }
159    // NOTE(unsafe): Assuming the address symbols from the linker as well as
160    // the source instruction data are all valid, this is safe as it only
161    // copies linker-prepared data to where the code expects it to be.
162    // Calling it multiple times is safe as well.
163
164    unsafe {
165        // ITCM is enabled on reset on our CPU but might not be on others.
166        // Keep for completeness.
167        const ITCMCR: *mut u32 = 0xE000_EF90usize as _;
168        ptr::write_volatile(ITCMCR, ptr::read_volatile(ITCMCR) | 1);
169
170        // Ensure ITCM is enabled before loading.
171        atomic::fence(Ordering::SeqCst);
172
173        let sitcm = core::ptr::addr_of_mut!(__sitcm);
174        let eitcm = core::ptr::addr_of_mut!(__eitcm);
175        let siitcm = core::ptr::addr_of_mut!(__siitcm);
176
177        let len = eitcm.offset_from(sitcm) as usize;
178        let dst = slice::from_raw_parts_mut(sitcm, len);
179        let src = slice::from_raw_parts(siitcm, len);
180        // Load code into ITCM.
181        dst.copy_from_slice(src);
182    }
183
184    // Ensure ITCM is loaded before potentially executing any instructions from it.
185    atomic::fence(Ordering::SeqCst);
186    cortex_m::asm::dsb();
187    cortex_m::asm::isb();
188}
189
190/// Configure the stabilizer hardware for operation.
191///
192/// # Note
193/// Refer to [design_parameters::TIMER_FREQUENCY] to determine the frequency of the sampling timer.
194///
195/// # Args
196/// * `core` - The cortex-m peripherals.
197/// * `device` - The microcontroller peripherals to be configured.
198/// * `clock` - A `SystemTimer` implementing `Clock`.
199/// * `batch_size` - The size of each ADC/DAC batch.
200/// * `sample_ticks` - The number of timer ticks between each sample.
201///
202/// # Returns
203/// (stabilizer, pounder) where `stabilizer` is a `StabilizerDevices` structure containing all
204/// stabilizer hardware interfaces in a disabled state. `pounder` is an `Option` containing
205/// `Some(devices)` if pounder is detected, where `devices` is a `PounderDevices` structure
206/// containing all of the pounder hardware interfaces in a disabled state.
207pub fn setup<C, const Y: usize>(
208    mut core: stm32h7xx_hal::stm32::CorePeripherals,
209    device: stm32h7xx_hal::stm32::Peripherals,
210    clock: SystemTimer,
211    batch_size: usize,
212    sample_ticks: u32,
213) -> (StabilizerDevices<C, Y>, Option<PounderDevices>)
214where
215    C: serial_settings::Settings + AppSettings,
216{
217    // Set up RTT logging
218    {
219        // Enable debug during WFE/WFI-induced sleep
220        device.DBGMCU.cr.modify(|_, w| w.dbgsleep_d1().set_bit());
221
222        // Set up RTT channel to use for `rprintln!()` as "best effort".
223        // This removes a critical section around the logging and thus allows
224        // high-prio tasks to always interrupt at low latency.
225        // It comes at a cost:
226        // If a high-priority tasks preempts while we are logging something,
227        // and if we then also want to log from within that high-preiority task,
228        // the high-prio log message will be lost.
229
230        let channels = rtt_target::rtt_init_default!();
231        // Note(unsafe): The closure we pass does not establish a critical section
232        // as demanded but it does ensure synchronization and implements a lock.
233        unsafe {
234            rtt_target::set_print_channel_cs(
235                channels.up.0,
236                &((|arg, f| {
237                    static LOCKED: AtomicBool = AtomicBool::new(false);
238                    if LOCKED.compare_exchange_weak(
239                        false,
240                        true,
241                        Ordering::Acquire,
242                        Ordering::Relaxed,
243                    ) == Ok(false)
244                    {
245                        f(arg);
246                        LOCKED.store(false, Ordering::Release);
247                    }
248                }) as rtt_target::CriticalSectionFunc),
249            );
250        }
251
252        static LOGGER: rtt_logger::RTTLogger =
253            rtt_logger::RTTLogger::new(log::LevelFilter::Info);
254        log::set_logger(&LOGGER)
255            .map(|()| log::set_max_level(log::LevelFilter::Trace))
256            .unwrap();
257        log::info!("Starting");
258    }
259
260    // Check for a reboot to DFU before doing any system configuration.
261    if platform::dfu_bootflag() {
262        platform::execute_system_bootloader();
263    }
264
265    let pwr = device.PWR.constrain();
266    let vos = pwr.freeze();
267
268    // Enable SRAM3 for the ethernet descriptor ring.
269    device.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit());
270
271    // Clear reset flags.
272    device.RCC.rsr.write(|w| w.rmvf().set_bit());
273
274    // Select the PLLs for SPI.
275    device
276        .RCC
277        .d2ccip1r
278        .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q());
279
280    device.RCC.d1ccipr.modify(|_, w| w.qspisel().rcc_hclk3());
281
282    device
283        .RCC
284        .d3ccipr
285        .modify(|_, w| w.adcsel().per().spi6sel().pll2_q());
286
287    let rcc = device.RCC.constrain();
288    let mut ccdr = rcc
289        .use_hse(8.MHz())
290        .sysclk(design_parameters::SYSCLK.convert())
291        .hclk(200.MHz())
292        .per_ck(64.MHz()) // fixed frequency HSI, only used for internal ADC. This is not the "peripheral" clock for timers and others.
293        .pll2_p_ck(100.MHz())
294        .pll2_q_ck(100.MHz())
295        .freeze(vos, &device.SYSCFG);
296
297    // Set up USB clocks.
298    ccdr.clocks.hsi48_ck().unwrap();
299    ccdr.peripheral
300        .kernel_usb_clk_mux(stm32h7xx_hal::rcc::rec::UsbClkSel::Hsi48);
301
302    // Before being able to call any code in ITCM, load that code from flash.
303    load_itcm();
304
305    Systick::start(core.SYST, ccdr.clocks.sysclk().to_Hz());
306
307    // After ITCM loading.
308    core.SCB.enable_icache();
309
310    let mut delay = delay::AsmDelay::new(ccdr.clocks.c_ck().to_Hz());
311
312    let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA);
313    let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB);
314    let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC);
315    let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD);
316    let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE);
317    let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF);
318    let mut gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG);
319
320    let dma_streams =
321        hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
322
323    // Verify that batch period does not exceed RTIC Monotonic timer period.
324    assert!(
325        (batch_size as u32 * sample_ticks) as f32
326            * design_parameters::TIMER_PERIOD
327            * (super::MONOTONIC_FREQUENCY as f32)
328            < 1.
329    );
330
331    // Configure timer 2 to trigger conversions for the ADC
332    let mut sampling_timer = {
333        // The timer frequency is manually adjusted below, so the 1KHz setting here is a
334        // dont-care.
335        let mut timer2 =
336            device
337                .TIM2
338                .timer(1.kHz(), ccdr.peripheral.TIM2, &ccdr.clocks);
339
340        // Configure the timer to count at the designed tick rate. We will manually set the
341        // period below.
342        timer2.pause();
343        timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
344
345        let mut sampling_timer = timers::SamplingTimer::new(timer2);
346        sampling_timer.set_period_ticks(sample_ticks - 1);
347
348        // The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
349        // it generates a trigger whenever it is enabled.
350
351        sampling_timer
352    };
353
354    let mut shadow_sampling_timer = {
355        // The timer frequency is manually adjusted below, so the 1KHz setting here is a
356        // dont-care.
357        let mut timer3 =
358            device
359                .TIM3
360                .timer(1.kHz(), ccdr.peripheral.TIM3, &ccdr.clocks);
361
362        // Configure the timer to count at the designed tick rate. We will manually set the
363        // period below.
364        timer3.pause();
365        timer3.reset_counter();
366        timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
367
368        let mut shadow_sampling_timer =
369            timers::ShadowSamplingTimer::new(timer3);
370        shadow_sampling_timer.set_period_ticks(sample_ticks as u16 - 1);
371
372        // The shadow sampling timer is a slave-mode timer to the sampling timer. It should
373        // always be in-sync - thus, we configure it to operate in slave mode using "Trigger
374        // mode".
375        // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the
376        // SamplingTimer start now gates the start of the ShadowSamplingTimer.
377        shadow_sampling_timer.set_slave_mode(
378            timers::TriggerSource::Trigger1,
379            timers::SlaveMode::Trigger,
380        );
381
382        shadow_sampling_timer
383    };
384
385    let sampling_timer_channels = sampling_timer.channels();
386    let shadow_sampling_timer_channels = shadow_sampling_timer.channels();
387
388    let mut timestamp_timer = {
389        // The timer frequency is manually adjusted below, so the 1KHz setting here is a
390        // dont-care.
391        let mut timer5 =
392            device
393                .TIM5
394                .timer(1.kHz(), ccdr.peripheral.TIM5, &ccdr.clocks);
395
396        // Configure the timer to count at the designed tick rate. We will manually set the
397        // period below.
398        timer5.pause();
399        timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY.convert());
400
401        // The timestamp timer runs at the counter cycle period as the sampling timers.
402        // To accomodate this, we manually set the prescaler identical to the sample
403        // timer, but use maximum overflow period.
404        let mut timer = timers::TimestampTimer::new(timer5);
405
406        // TODO: Check hardware synchronization of timestamping and the sampling timers
407        // for phase shift determinism.
408
409        timer.set_period_ticks(u32::MAX);
410
411        timer
412    };
413
414    let timestamp_timer_channels = timestamp_timer.channels();
415
416    // Configure the SPI interfaces to the ADCs and DACs.
417    let adcs = {
418        let adc0 = {
419            let miso = gpiob.pb14.into_alternate().speed(Speed::VeryHigh);
420            let sck = gpiob.pb10.into_alternate().speed(Speed::VeryHigh);
421            let nss = gpiob.pb9.into_alternate().speed(Speed::VeryHigh);
422
423            let config = hal::spi::Config::new(hal::spi::Mode {
424                polarity: hal::spi::Polarity::IdleHigh,
425                phase: hal::spi::Phase::CaptureOnSecondTransition,
426            })
427            .hardware_cs(hal::spi::HardwareCS {
428                mode: hal::spi::HardwareCSMode::WordTransaction,
429                assertion_delay: design_parameters::ADC_SETUP_TIME,
430                polarity: hal::spi::Polarity::IdleHigh,
431            })
432            .communication_mode(hal::spi::CommunicationMode::Receiver);
433
434            let spi: hal::spi::Spi<_, _, u16> = device.SPI2.spi(
435                (sck, miso, hal::spi::NoMosi, nss),
436                config,
437                design_parameters::ADC_DAC_SCK_MAX.convert(),
438                ccdr.peripheral.SPI2,
439                &ccdr.clocks,
440            );
441
442            adc::Adc0Input::new(
443                spi,
444                dma_streams.0,
445                dma_streams.1,
446                dma_streams.2,
447                sampling_timer_channels.ch1,
448                shadow_sampling_timer_channels.ch1,
449                batch_size,
450            )
451        };
452
453        let adc1 = {
454            let miso = gpiob.pb4.into_alternate().speed(Speed::VeryHigh);
455            let sck = gpioc.pc10.into_alternate().speed(Speed::VeryHigh);
456            let nss = gpioa.pa15.into_alternate().speed(Speed::VeryHigh);
457
458            let config = hal::spi::Config::new(hal::spi::Mode {
459                polarity: hal::spi::Polarity::IdleHigh,
460                phase: hal::spi::Phase::CaptureOnSecondTransition,
461            })
462            .hardware_cs(hal::spi::HardwareCS {
463                mode: hal::spi::HardwareCSMode::WordTransaction,
464                assertion_delay: design_parameters::ADC_SETUP_TIME,
465                polarity: hal::spi::Polarity::IdleHigh,
466            })
467            .communication_mode(hal::spi::CommunicationMode::Receiver);
468
469            let spi: hal::spi::Spi<_, _, u16> = device.SPI3.spi(
470                (sck, miso, hal::spi::NoMosi, nss),
471                config,
472                design_parameters::ADC_DAC_SCK_MAX.convert(),
473                ccdr.peripheral.SPI3,
474                &ccdr.clocks,
475            );
476
477            adc::Adc1Input::new(
478                spi,
479                dma_streams.3,
480                dma_streams.4,
481                dma_streams.5,
482                sampling_timer_channels.ch2,
483                shadow_sampling_timer_channels.ch2,
484                batch_size,
485            )
486        };
487
488        (adc0, adc1)
489    };
490
491    let dacs = {
492        let mut dac_clr_n = gpioe.pe12.into_push_pull_output();
493        dac_clr_n.set_high();
494
495        let dac0_spi = {
496            let miso = gpioe.pe5.into_alternate().speed(Speed::VeryHigh);
497            let sck = gpioe.pe2.into_alternate().speed(Speed::VeryHigh);
498            let nss = gpioe.pe4.into_alternate().speed(Speed::VeryHigh);
499
500            let config = hal::spi::Config::new(hal::spi::Mode {
501                polarity: hal::spi::Polarity::IdleHigh,
502                phase: hal::spi::Phase::CaptureOnSecondTransition,
503            })
504            .hardware_cs(hal::spi::HardwareCS {
505                mode: hal::spi::HardwareCSMode::WordTransaction,
506                assertion_delay: 0.0,
507                polarity: hal::spi::Polarity::IdleHigh,
508            })
509            .communication_mode(hal::spi::CommunicationMode::Transmitter)
510            .swap_mosi_miso();
511
512            device.SPI4.spi(
513                (sck, miso, hal::spi::NoMosi, nss),
514                config,
515                design_parameters::ADC_DAC_SCK_MAX.convert(),
516                ccdr.peripheral.SPI4,
517                &ccdr.clocks,
518            )
519        };
520
521        let dac1_spi = {
522            let miso = gpiof.pf8.into_alternate().speed(Speed::VeryHigh);
523            let sck = gpiof.pf7.into_alternate().speed(Speed::VeryHigh);
524            let nss = gpiof.pf6.into_alternate().speed(Speed::VeryHigh);
525
526            let config = hal::spi::Config::new(hal::spi::Mode {
527                polarity: hal::spi::Polarity::IdleHigh,
528                phase: hal::spi::Phase::CaptureOnSecondTransition,
529            })
530            .hardware_cs(hal::spi::HardwareCS {
531                mode: hal::spi::HardwareCSMode::WordTransaction,
532                assertion_delay: 0.0,
533                polarity: hal::spi::Polarity::IdleHigh,
534            })
535            .communication_mode(hal::spi::CommunicationMode::Transmitter)
536            .swap_mosi_miso();
537
538            device.SPI5.spi(
539                (sck, miso, hal::spi::NoMosi, nss),
540                config,
541                design_parameters::ADC_DAC_SCK_MAX.convert(),
542                ccdr.peripheral.SPI5,
543                &ccdr.clocks,
544            )
545        };
546
547        let dac0 = dac::Dac0Output::new(
548            dac0_spi,
549            dma_streams.6,
550            sampling_timer_channels.ch3,
551            batch_size,
552        );
553        let dac1 = dac::Dac1Output::new(
554            dac1_spi,
555            dma_streams.7,
556            sampling_timer_channels.ch4,
557            batch_size,
558        );
559
560        dac_clr_n.set_low();
561        // dac0_ldac_n
562        gpioe.pe11.into_push_pull_output().set_low();
563        // dac1_ldac_n
564        gpioe.pe15.into_push_pull_output().set_low();
565        dac_clr_n.set_high();
566
567        (dac0, dac1)
568    };
569
570    let afes = {
571        // AFE_PWR_ON on hardware revision v1.3.2
572        gpioe.pe1.into_push_pull_output().set_high();
573
574        let afe0 = {
575            let a0_pin = gpiof.pf2.into_push_pull_output();
576            let a1_pin = gpiof.pf5.into_push_pull_output();
577            afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
578        };
579
580        let afe1 = {
581            let a0_pin = gpiod.pd14.into_push_pull_output();
582            let a1_pin = gpiod.pd15.into_push_pull_output();
583            afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
584        };
585
586        (afe0, afe1)
587    };
588
589    let input_stamper = {
590        let trigger = gpioa.pa3.into_alternate();
591        InputStamper::new(trigger, timestamp_timer_channels.ch4)
592    };
593
594    let digital_inputs = {
595        let di0 = gpiog.pg9.into_floating_input();
596        let di1 = gpioc.pc15.into_floating_input();
597        (di0, di1)
598    };
599
600    let mut eeprom_i2c = {
601        let sda = gpiof.pf0.into_alternate().set_open_drain();
602        let scl = gpiof.pf1.into_alternate().set_open_drain();
603        device.I2C2.i2c(
604            (scl, sda),
605            100.kHz(),
606            ccdr.peripheral.I2C2,
607            &ccdr.clocks,
608        )
609    };
610
611    let metadata = {
612        // Read the hardware version pins.
613        let hardware_version = {
614            let hwrev0 = gpiog.pg0.into_pull_down_input();
615            let hwrev1 = gpiog.pg1.into_pull_down_input();
616            let hwrev2 = gpiog.pg2.into_pull_down_input();
617            let hwrev3 = gpiog.pg3.into_pull_down_input();
618
619            HardwareVersion::from(
620                *0u8.set_bit(0, hwrev0.is_high())
621                    .set_bit(1, hwrev1.is_high())
622                    .set_bit(2, hwrev2.is_high())
623                    .set_bit(3, hwrev3.is_high()),
624            )
625        };
626
627        ApplicationMetadata::new(hardware_version)
628    };
629
630    let mac_addr = smoltcp::wire::EthernetAddress(eeprom::read_eui48(
631        &mut eeprom_i2c,
632        &mut delay,
633    ));
634    log::info!("EUI48: {}", mac_addr);
635
636    let (flash, mut settings) = {
637        let mut flash = {
638            let (_, flash_bank2) = device.FLASH.split();
639            super::flash::Flash(flash_bank2.unwrap())
640        };
641
642        let mut settings = C::new(NetSettings::new(mac_addr));
643        crate::settings::SerialSettingsPlatform::load(
644            &mut settings,
645            &mut flash,
646        );
647        (flash, settings)
648    };
649
650    let network_devices = {
651        let ethernet_pins = {
652            // Reset the PHY before configuring pins.
653            let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output();
654            eth_phy_nrst.set_low();
655            delay.delay_us(200u8);
656            eth_phy_nrst.set_high();
657
658            let ref_clk = gpioa.pa1.into_alternate().speed(Speed::VeryHigh);
659            let mdio = gpioa.pa2.into_alternate().speed(Speed::VeryHigh);
660            let mdc = gpioc.pc1.into_alternate().speed(Speed::VeryHigh);
661            let crs_dv = gpioa.pa7.into_alternate().speed(Speed::VeryHigh);
662            let rxd0 = gpioc.pc4.into_alternate().speed(Speed::VeryHigh);
663            let rxd1 = gpioc.pc5.into_alternate().speed(Speed::VeryHigh);
664            let tx_en = gpiob.pb11.into_alternate().speed(Speed::VeryHigh);
665            let txd0 = gpiob.pb12.into_alternate().speed(Speed::VeryHigh);
666            let txd1 = gpiog.pg14.into_alternate().speed(Speed::VeryHigh);
667
668            (ref_clk, mdio, mdc, crs_dv, rxd0, rxd1, tx_en, txd0, txd1)
669        };
670
671        let ring = unsafe { DES_RING.write(ethernet::DesRing::new()) };
672
673        // Configure the ethernet controller
674        let (mut eth_dma, eth_mac) = ethernet::new(
675            device.ETHERNET_MAC,
676            device.ETHERNET_MTL,
677            device.ETHERNET_DMA,
678            ethernet_pins,
679            // Note(unsafe): We only call this function once to take ownership of the
680            // descriptor ring.
681            ring,
682            mac_addr,
683            ccdr.peripheral.ETH1MAC,
684            &ccdr.clocks,
685        );
686
687        // Reset and initialize the ethernet phy.
688        let mut lan8742a =
689            ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
690        lan8742a.phy_reset();
691        lan8742a.phy_init();
692
693        unsafe { ethernet::enable_interrupt() };
694
695        // Configure IP address according to DHCP socket availability
696        let ip_addrs: smoltcp::wire::IpAddress = match settings.net().ip.parse()
697        {
698            Ok(addr) => addr,
699            Err(e) => {
700                log::warn!("Invalid IP address in settings: {e:?}. Defaulting to 0.0.0.0 (DHCP)");
701                "0.0.0.0".parse().unwrap()
702            }
703        };
704
705        let random_seed = {
706            let mut rng =
707                device.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks);
708            let mut data = [0u8; 8];
709            rng.fill(&mut data).unwrap();
710            data
711        };
712
713        // Note(unwrap): The hardware configuration function is only allowed to be called once.
714        // Unwrapping is intended to panic if called again to prevent re-use of global memory.
715        let store =
716            cortex_m::singleton!(: NetStorage = NetStorage::default()).unwrap();
717
718        store.ip_addrs[0] = smoltcp::wire::IpCidr::new(ip_addrs, 24);
719
720        let mut ethernet_config = smoltcp::iface::Config::new(
721            smoltcp::wire::HardwareAddress::Ethernet(mac_addr),
722        );
723        ethernet_config.random_seed = u64::from_be_bytes(random_seed);
724
725        let mut interface = smoltcp::iface::Interface::new(
726            ethernet_config,
727            &mut eth_dma,
728            smoltcp::time::Instant::ZERO,
729        );
730
731        interface
732            .routes_mut()
733            .add_default_ipv4_route(smoltcp::wire::Ipv4Address::UNSPECIFIED)
734            .unwrap();
735
736        interface.update_ip_addrs(|ref mut addrs| {
737            if !ip_addrs.is_unspecified() {
738                addrs
739                    .push(smoltcp::wire::IpCidr::new(ip_addrs, 24))
740                    .unwrap();
741            }
742        });
743
744        let mut sockets =
745            smoltcp::iface::SocketSet::new(&mut store.sockets[..]);
746        for storage in store.tcp_socket_storage[..].iter_mut() {
747            let tcp_socket = {
748                let rx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
749                    &mut storage.rx_storage[..],
750                );
751                let tx_buffer = smoltcp::socket::tcp::SocketBuffer::new(
752                    &mut storage.tx_storage[..],
753                );
754
755                smoltcp::socket::tcp::Socket::new(rx_buffer, tx_buffer)
756            };
757
758            sockets.add(tcp_socket);
759        }
760
761        if ip_addrs.is_unspecified() {
762            sockets.add(smoltcp::socket::dhcpv4::Socket::new());
763        }
764
765        sockets.add(smoltcp::socket::dns::Socket::new(
766            &[],
767            &mut store.dns_storage[..],
768        ));
769
770        for storage in store.udp_socket_storage[..].iter_mut() {
771            let udp_socket = {
772                let rx_buffer = smoltcp::socket::udp::PacketBuffer::new(
773                    &mut storage.rx_metadata[..],
774                    &mut storage.rx_storage[..],
775                );
776                let tx_buffer = smoltcp::socket::udp::PacketBuffer::new(
777                    &mut storage.tx_metadata[..],
778                    &mut storage.tx_storage[..],
779                );
780
781                smoltcp::socket::udp::Socket::new(rx_buffer, tx_buffer)
782            };
783
784            sockets.add(udp_socket);
785        }
786
787        let mut stack =
788            smoltcp_nal::NetworkStack::new(interface, eth_dma, sockets, clock);
789
790        stack.seed_random_port(&random_seed);
791
792        NetworkDevices {
793            stack,
794            phy: lan8742a,
795            mac_address: mac_addr,
796        }
797    };
798
799    let mut fp_led_0 = gpiod.pd5.into_push_pull_output();
800    let mut fp_led_1 = gpiod.pd6.into_push_pull_output();
801    let mut fp_led_2 = gpiog.pg4.into_push_pull_output();
802    let mut fp_led_3 = gpiod.pd12.into_push_pull_output();
803
804    fp_led_0.set_low();
805    fp_led_1.set_low();
806    fp_led_2.set_low();
807    fp_led_3.set_low();
808
809    let (adc1, adc2, adc3) = {
810        let (mut adc1, mut adc2) = hal::adc::adc12(
811            device.ADC1,
812            device.ADC2,
813            stm32h7xx_hal::time::Hertz::MHz(25),
814            &mut delay,
815            ccdr.peripheral.ADC12,
816            &ccdr.clocks,
817        );
818        let mut adc3 = hal::adc::Adc::adc3(
819            device.ADC3,
820            stm32h7xx_hal::time::Hertz::MHz(25),
821            &mut delay,
822            ccdr.peripheral.ADC3,
823            &ccdr.clocks,
824        );
825
826        adc1.set_sample_time(hal::adc::AdcSampleTime::T_810);
827        adc1.set_resolution(hal::adc::Resolution::SixteenBit);
828        adc1.calibrate();
829        adc2.set_sample_time(hal::adc::AdcSampleTime::T_810);
830        adc2.set_resolution(hal::adc::Resolution::SixteenBit);
831        adc2.calibrate();
832        adc3.set_sample_time(hal::adc::AdcSampleTime::T_810);
833        adc3.set_resolution(hal::adc::Resolution::SixteenBit);
834        adc3.calibrate();
835
836        hal::adc::Temperature::new().enable(&adc3);
837
838        let adc1 = adc1.enable();
839        let adc2 = adc2.enable();
840        let adc3 = adc3.enable();
841
842        (
843            // The ADCs must live as global, mutable singletons so that we can hand out references
844            // to the internal ADC. If they were instead to live within e.g. StabilizerDevices,
845            // they would not yet live in 'static memory, which means that we could not hand out
846            // references during initialization, since those references would be invalidated when
847            // we move StabilizerDevices into the late RTIC resources.
848            cortex_m::singleton!(: SharedAdc<hal::stm32::ADC1> = SharedAdc::new(adc1.slope() as f32, adc1)).unwrap(),
849            cortex_m::singleton!(: SharedAdc<hal::stm32::ADC2> = SharedAdc::new(adc2.slope() as f32, adc2)).unwrap(),
850            cortex_m::singleton!(: SharedAdc<hal::stm32::ADC3> = SharedAdc::new(adc3.slope() as f32, adc3)).unwrap(),
851        )
852    };
853
854    // Measure the Pounder PGOOD output to detect if pounder is present on Stabilizer.
855    let pounder_pgood = gpiob.pb13.into_pull_down_input();
856    delay.delay_ms(2u8);
857    let pounder = if pounder_pgood.is_high() {
858        log::info!("Found Pounder");
859
860        let i2c1 = {
861            let sda = gpiob.pb7.into_alternate().set_open_drain();
862            let scl = gpiob.pb8.into_alternate().set_open_drain();
863            let i2c1 = device.I2C1.i2c(
864                (scl, sda),
865                400.kHz(),
866                ccdr.peripheral.I2C1,
867                &ccdr.clocks,
868            );
869
870            shared_bus::new_atomic_check!(hal::i2c::I2c<hal::stm32::I2C1> = i2c1).unwrap()
871        };
872
873        let spi = {
874            let mosi = gpiod.pd7.into_alternate();
875            let miso = gpioa.pa6.into_alternate();
876            let sck = gpiog.pg11.into_alternate();
877
878            let config = hal::spi::Config::new(hal::spi::Mode {
879                polarity: hal::spi::Polarity::IdleHigh,
880                phase: hal::spi::Phase::CaptureOnSecondTransition,
881            });
882
883            // The maximum frequency of this SPI must be limited due to capacitance on the MISO
884            // line causing a long RC decay.
885            device.SPI1.spi(
886                (sck, miso, mosi),
887                config,
888                5.MHz(),
889                ccdr.peripheral.SPI1,
890                &ccdr.clocks,
891            )
892        };
893
894        let pwr0 = adc1.create_channel(gpiof.pf11.into_analog());
895        let pwr1 = adc2.create_channel(gpiof.pf14.into_analog());
896        let aux_adc0 = adc3.create_channel(gpiof.pf3.into_analog());
897        let aux_adc1 = adc3.create_channel(gpiof.pf4.into_analog());
898
899        let pounder_devices = pounder::PounderDevices::new(
900            i2c1.acquire_i2c(),
901            spi,
902            (pwr0, pwr1),
903            (aux_adc0, aux_adc1),
904        )
905        .unwrap();
906
907        let ad9959 = {
908            let qspi_interface = {
909                // Instantiate the QUADSPI pins and peripheral interface.
910                let qspi_pins = {
911                    let _ncs =
912                        gpioc.pc11.into_alternate::<9>().speed(Speed::VeryHigh);
913
914                    let clk = gpiob.pb2.into_alternate().speed(Speed::VeryHigh);
915                    let io0 = gpioe.pe7.into_alternate().speed(Speed::VeryHigh);
916                    let io1 = gpioe.pe8.into_alternate().speed(Speed::VeryHigh);
917                    let io2 = gpioe.pe9.into_alternate().speed(Speed::VeryHigh);
918                    let io3 =
919                        gpioe.pe10.into_alternate().speed(Speed::VeryHigh);
920
921                    (clk, io0, io1, io2, io3)
922                };
923
924                let qspi = device.QUADSPI.bank2(
925                    qspi_pins,
926                    design_parameters::POUNDER_QSPI_FREQUENCY.convert(),
927                    &ccdr.clocks,
928                    ccdr.peripheral.QSPI,
929                );
930
931                pounder::QspiInterface::new(qspi).unwrap()
932            };
933
934            #[cfg(not(feature = "pounder_v1_0"))]
935            let reset_pin = gpiog.pg6.into_push_pull_output();
936            #[cfg(feature = "pounder_v1_0")]
937            let reset_pin = gpioa.pa0.into_push_pull_output();
938
939            let mut io_update = gpiog.pg7.into_push_pull_output();
940
941            // Delay to allow the pounder DDS reference clock to fully start up. The exact startup
942            // time is not specified, but bench testing indicates it usually comes up within
943            // 200-300uS. We do a larger delay to ensure that it comes up and is stable before
944            // using it.
945            delay.delay_ms(10u32);
946
947            let mut ad9959 = ad9959::Ad9959::new(
948                qspi_interface,
949                reset_pin,
950                &mut io_update,
951                &mut delay,
952                ad9959::Mode::FourBitSerial,
953                design_parameters::DDS_REF_CLK.to_Hz() as f32,
954                design_parameters::DDS_MULTIPLIER,
955            )
956            .unwrap();
957
958            ad9959.self_test().unwrap();
959
960            // Return IO_Update
961            gpiog.pg7 = io_update.into_analog();
962
963            ad9959
964        };
965
966        let dds_output = {
967            let io_update_trigger = {
968                let _io_update =
969                    gpiog.pg7.into_alternate::<2>().speed(Speed::VeryHigh);
970
971                // Configure the IO_Update signal for the DDS.
972                let mut hrtimer = pounder::hrtimer::HighResTimerE::new(
973                    device.HRTIM_TIME,
974                    device.HRTIM_MASTER,
975                    device.HRTIM_COMMON,
976                    ccdr.clocks,
977                    ccdr.peripheral.HRTIM,
978                );
979
980                // IO_Update occurs after a fixed delay from the QSPI write. Note that the timer
981                // is triggered after the QSPI write, which can take approximately 120nS, so
982                // there is additional margin.
983                hrtimer.configure_single_shot(
984                    pounder::hrtimer::Channel::Two,
985                    design_parameters::POUNDER_IO_UPDATE_DELAY,
986                    design_parameters::POUNDER_IO_UPDATE_DURATION,
987                );
988
989                // Ensure that we have enough time for an IO-update every batch.
990                let sample_frequency = {
991                    design_parameters::TIMER_FREQUENCY.to_Hz() as f32
992                        / sample_ticks as f32
993                };
994
995                let sample_period = 1.0 / sample_frequency;
996                assert!(
997                    sample_period * batch_size as f32
998                        > design_parameters::POUNDER_IO_UPDATE_DELAY
999                );
1000
1001                hrtimer
1002            };
1003
1004            let (qspi, config) = ad9959.freeze();
1005            DdsOutput::new(qspi, io_update_trigger, config)
1006        };
1007
1008        #[cfg(not(feature = "pounder_v1_0"))]
1009        let pounder_stamper = {
1010            log::info!("Assuming Pounder v1.1 or later");
1011            let etr_pin = gpioa.pa0.into_alternate();
1012
1013            // The frequency in the constructor is dont-care, as we will modify the period + clock
1014            // source manually below.
1015            let tim8 =
1016                device
1017                    .TIM8
1018                    .timer(1.kHz(), ccdr.peripheral.TIM8, &ccdr.clocks);
1019            let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8);
1020
1021            // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
1022            // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
1023            // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
1024            // This is less than fCK_INT/3 of the timer as required for oversampling the trigger.
1025            timestamp_timer.set_external_clock(timers::Prescaler::Div4);
1026            timestamp_timer.start();
1027
1028            // Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
1029            // Scale and wrap before or after the PLL.
1030            timestamp_timer.set_period_ticks(u16::MAX);
1031            let tim8_channels = timestamp_timer.channels();
1032
1033            pounder::timestamp::Timestamper::new(
1034                timestamp_timer,
1035                tim8_channels.ch1,
1036                &mut sampling_timer,
1037                etr_pin,
1038                batch_size,
1039            )
1040        };
1041
1042        Some(PounderDevices {
1043            pounder: pounder_devices,
1044            dds_output,
1045
1046            #[cfg(not(feature = "pounder_v1_0"))]
1047            timestamper: pounder_stamper,
1048        })
1049    } else {
1050        None
1051    };
1052
1053    #[derive(Copy, Clone, Debug, PartialEq)]
1054    pub enum PoePower {
1055        /// No Power over Ethernet detected
1056        Absent,
1057        /// 802.3af (12.95 W) Power over Ethernet present
1058        Low,
1059        /// 802.3at (25.5 W) Power over Ethernet present
1060        High,
1061    }
1062
1063    let poe_src_status = gpiob.pb6.into_floating_input();
1064    let poe_at_event = gpioc.pc14.into_floating_input();
1065    let poe = match (poe_src_status.is_high(), poe_at_event.is_high()) {
1066        (false, _) => PoePower::Absent,
1067        (true, false) => PoePower::Low,
1068        (true, true) => PoePower::High,
1069    };
1070    log::info!("PoE: {poe:?}",);
1071
1072    let mut force_eem_source = gpioe.pe0.into_push_pull_output();
1073
1074    // checks whether a pin can be weakly pulled high an low
1075    fn check_input<const P: char, const N: u8>(
1076        pin: hal::gpio::Pin<P, N, hal::gpio::Analog>,
1077        mut is_floating: bool,
1078        delay: &mut AsmDelay,
1079    ) -> (hal::gpio::Pin<P, N, hal::gpio::Analog>, bool) {
1080        let pin = pin.into_pull_up_input();
1081        delay.delay_ms(1u8);
1082        is_floating &= pin.is_high();
1083        let pin = pin.into_pull_down_input();
1084        delay.delay_ms(1u8);
1085        is_floating &= pin.is_low();
1086        (pin.into_analog(), is_floating)
1087    }
1088
1089    let mut lvds0 = gpiog.pg13;
1090    let mut lvds1 = gpiob.pb5;
1091    let mut lvds3 = gpiog.pg8;
1092    let mut is_floating = true;
1093    // Default EEM population: LVDS0/1/3 driven by LVDS receivers
1094    (lvds0, is_floating) = check_input(lvds0, is_floating, &mut delay);
1095    (lvds1, is_floating) = check_input(lvds1, is_floating, &mut delay);
1096    (lvds3, is_floating) = check_input(lvds3, is_floating, &mut delay);
1097    let eem = if !is_floating {
1098        log::info!("EEM population variant: Default detected");
1099        force_eem_source.set_low();
1100        Eem::Gpio(Gpio {
1101            lvds4: gpiod.pd1.into_floating_input(),
1102            lvds5: gpiod.pd2.into_floating_input(),
1103            lvds6: gpiod.pd3.into_push_pull_output(),
1104            lvds7: gpiod.pd4.into_push_pull_output(),
1105        })
1106    } else {
1107        log::info!("EEM population variant: Output detected");
1108        assert!(poe != PoePower::Low);
1109        force_eem_source.set_high();
1110        delay.delay_ms(200u8);
1111
1112        let spi = device
1113            .SPI6
1114            .spi(
1115                (
1116                    lvds0.into_alternate(),      // SCK
1117                    gpiog.pg12.into_alternate(), // MISO/SDO
1118                    lvds1.into_alternate(),      // MOSI/SDI
1119                ),
1120                hal::spi::MODE_0,
1121                20.MHz(),
1122                ccdr.peripheral.SPI6,
1123                &ccdr.clocks,
1124            )
1125            .forward();
1126        let spi = cortex_m::singleton!(:
1127            RefCell<Forward<hal::spi::Spi<hal::stm32::SPI6, hal::spi::Enabled>>> =
1128                RefCell::new(spi)).unwrap();
1129
1130        let cs = [
1131            lvds3.into_push_pull_output().erase().forward(),
1132            gpiod.pd1.into_push_pull_output().erase().forward(),
1133            gpiod.pd2.into_push_pull_output().erase().forward(),
1134        ];
1135        let cs = cortex_m::singleton!(:
1136            RefCell<[Forward<hal::gpio::ErasedPin<hal::gpio::Output>, ForwardOutputPin>; 3]> =
1137                RefCell::new(cs)
1138        )
1139        .unwrap();
1140
1141        match urukul::Urukul::new(
1142            spi,
1143            cs,
1144            gpiod.pd3.into_push_pull_output().erase().forward(),
1145            gpiod.pd4.into_push_pull_output().erase().forward(),
1146        ) {
1147            Ok(urukul) => Eem::Urukul(urukul),
1148            Err(err) => {
1149                log::warn!("Urukul initialization failed: {err:?}");
1150                Eem::None
1151            }
1152        }
1153    };
1154
1155    let (usb_device, usb_serial) = {
1156        let _usb_id = gpioa.pa10.into_alternate::<10>();
1157        let usb_n = gpioa.pa11.into_alternate();
1158        let usb_p = gpioa.pa12.into_alternate();
1159        let usb = stm32h7xx_hal::usb_hs::USB2::new(
1160            device.OTG2_HS_GLOBAL,
1161            device.OTG2_HS_DEVICE,
1162            device.OTG2_HS_PWRCLK,
1163            usb_n,
1164            usb_p,
1165            ccdr.peripheral.USB2OTG,
1166            &ccdr.clocks,
1167        );
1168
1169        let endpoint_memory =
1170            cortex_m::singleton!(: Option<&'static mut [u32]> = None).unwrap();
1171        endpoint_memory.replace(
1172            &mut cortex_m::singleton!(: [u32; 1024] = [0; 1024]).unwrap()[..],
1173        );
1174        let usb_bus = cortex_m::singleton!(: usb_device::bus::UsbBusAllocator<super::UsbBus> =
1175        stm32h7xx_hal::usb_hs::UsbBus::new(
1176            usb,
1177            endpoint_memory.take().unwrap(),
1178        ))
1179        .unwrap();
1180
1181        let read_store = cortex_m::singleton!(: [u8; 128] = [0; 128]).unwrap();
1182        let write_store =
1183            cortex_m::singleton!(: [u8; 1024] = [0; 1024]).unwrap();
1184        let serial = usbd_serial::SerialPort::new_with_store(
1185            usb_bus,
1186            &mut read_store[..],
1187            &mut write_store[..],
1188        );
1189
1190        // Generate a device serial number from the MAC address.
1191        let serial_number = cortex_m::singleton!(: String<17> = {
1192            let mut s = String::new();
1193            write!(s, "{mac_addr}").unwrap();
1194            s
1195        })
1196        .unwrap();
1197
1198        let usb_device = usb_device::device::UsbDeviceBuilder::new(
1199            usb_bus,
1200            usb_device::device::UsbVidPid(0x1209, 0x392F),
1201        )
1202        .strings(&[usb_device::device::StringDescriptors::default()
1203            .manufacturer("ARTIQ/Sinara")
1204            .product("Stabilizer")
1205            .serial_number(serial_number)])
1206        .unwrap()
1207        .device_class(usbd_serial::USB_CLASS_CDC)
1208        .build();
1209
1210        (usb_device, serial)
1211    };
1212
1213    let usb_terminal = {
1214        let input_buffer =
1215            cortex_m::singleton!(: [u8; 128] = [0u8; 128]).unwrap();
1216        let serialize_buffer =
1217            cortex_m::singleton!(: [u8; 512] = [0u8; 512]).unwrap();
1218
1219        serial_settings::Runner::new(
1220            crate::settings::SerialSettingsPlatform {
1221                interface: serial_settings::BestEffortInterface::new(
1222                    usb_serial,
1223                ),
1224                storage: flash,
1225                metadata,
1226                _settings_marker: core::marker::PhantomData,
1227            },
1228            input_buffer,
1229            serialize_buffer,
1230            &mut settings,
1231        )
1232        .unwrap()
1233    };
1234
1235    let stabilizer = StabilizerDevices {
1236        afes,
1237        adcs,
1238        dacs,
1239        temperature_sensor: CpuTempSensor::new(
1240            adc3.create_channel(hal::adc::Temperature::new()),
1241        ),
1242        usb_serial: usb_terminal,
1243        timestamper: input_stamper,
1244        net: network_devices,
1245        adc_dac_timer: sampling_timer,
1246        timestamp_timer,
1247        digital_inputs,
1248        eem,
1249        usb: usb_device,
1250        metadata,
1251        settings,
1252    };
1253
1254    // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap());
1255    // info!("Built on {}", build_info::BUILT_TIME_UTC);
1256    // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET);
1257    log::info!("setup() complete");
1258
1259    (stabilizer, pounder)
1260}