#![no_std]
#![no_main]
use core::mem::MaybeUninit;
use core::sync::atomic::{fence, Ordering};
use miniconf::{Leaf, Tree};
use serde::{Deserialize, Serialize};
use rtic_monotonics::Monotonic;
use fugit::ExtU32;
use mutex_trait::prelude::*;
use idsp::iir;
use stabilizer::{
hardware::{
self,
adc::{Adc0Input, Adc1Input, AdcCode},
afe::Gain,
dac::{Dac0Output, Dac1Output, DacCode},
hal,
signal_generator::{self, SignalGenerator},
timers::SamplingTimer,
DigitalInput0, DigitalInput1, SerialTerminal, SystemTimer, Systick,
UsbDevice, AFE0, AFE1,
},
net::{
data_stream::{FrameGenerator, StreamFormat, StreamTarget},
telemetry::TelemetryBuffer,
NetworkState, NetworkUsers,
},
settings::NetSettings,
};
const SCALE: f32 = i16::MAX as _;
const IIR_CASCADE_LENGTH: usize = 1;
const BATCH_SIZE: usize = 8;
const SAMPLE_TICKS_LOG2: u8 = 7;
const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2;
const SAMPLE_PERIOD: f32 =
SAMPLE_TICKS as f32 * hardware::design_parameters::TIMER_PERIOD;
#[derive(Clone, Debug, Tree)]
pub struct Settings {
dual_iir: DualIir,
net: NetSettings,
}
impl stabilizer::settings::AppSettings for Settings {
fn new(net: NetSettings) -> Self {
Self {
net,
dual_iir: DualIir::default(),
}
}
fn net(&self) -> &NetSettings {
&self.net
}
}
impl serial_settings::Settings for Settings {
fn reset(&mut self) {
*self = Self {
dual_iir: DualIir::default(),
net: NetSettings::new(self.net.mac),
}
}
}
#[derive(Clone, Debug, Tree, Serialize, Deserialize)]
pub struct DualIir {
afe: [Leaf<Gain>; 2],
iir_ch: [[Leaf<iir::Biquad<f32>>; IIR_CASCADE_LENGTH]; 2],
allow_hold: Leaf<bool>,
force_hold: Leaf<bool>,
telemetry_period: Leaf<u16>,
stream: Leaf<StreamTarget>,
source: [signal_generator::BasicConfig; 2],
}
impl Default for DualIir {
fn default() -> Self {
let mut i = iir::Biquad::IDENTITY;
i.set_min(-SCALE);
i.set_max(SCALE);
Self {
afe: Default::default(),
iir_ch: [[i.into(); IIR_CASCADE_LENGTH]; 2],
allow_hold: false.into(),
force_hold: false.into(),
telemetry_period: 10.into(),
source: Default::default(),
stream: Default::default(),
}
}
}
#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, LTDC, SDMMC])]
mod app {
use super::*;
#[shared]
struct Shared {
usb: UsbDevice,
network: NetworkUsers<DualIir, 3>,
settings: Settings,
active_settings: DualIir,
telemetry: TelemetryBuffer,
source: [SignalGenerator; 2],
}
#[local]
struct Local {
usb_terminal: SerialTerminal<Settings, 4>,
sampling_timer: SamplingTimer,
digital_inputs: (DigitalInput0, DigitalInput1),
afes: (AFE0, AFE1),
adcs: (Adc0Input, Adc1Input),
dacs: (Dac0Output, Dac1Output),
iir_state: [[[f32; 4]; IIR_CASCADE_LENGTH]; 2],
generator: FrameGenerator,
cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor,
}
#[init]
fn init(c: init::Context) -> (Shared, Local) {
let clock = SystemTimer::new(|| Systick::now().ticks());
let (stabilizer, _pounder) = hardware::setup::setup::<Settings, 4>(
c.core,
c.device,
clock,
BATCH_SIZE,
SAMPLE_TICKS,
);
let mut network = NetworkUsers::new(
stabilizer.net.stack,
stabilizer.net.phy,
clock,
env!("CARGO_BIN_NAME"),
&stabilizer.settings.net,
stabilizer.metadata,
);
let generator = network.configure_streaming(StreamFormat::AdcDacData);
let shared = Shared {
usb: stabilizer.usb,
network,
active_settings: stabilizer.settings.dual_iir.clone(),
telemetry: TelemetryBuffer::default(),
source: [
SignalGenerator::new(
stabilizer.settings.dual_iir.source[0]
.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE)
.unwrap(),
),
SignalGenerator::new(
stabilizer.settings.dual_iir.source[1]
.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE)
.unwrap(),
),
],
settings: stabilizer.settings,
};
let mut local = Local {
usb_terminal: stabilizer.usb_serial,
sampling_timer: stabilizer.adc_dac_timer,
digital_inputs: stabilizer.digital_inputs,
afes: stabilizer.afes,
adcs: stabilizer.adcs,
dacs: stabilizer.dacs,
iir_state: [[[0.; 4]; IIR_CASCADE_LENGTH]; 2],
generator,
cpu_temp_sensor: stabilizer.temperature_sensor,
};
local.adcs.0.start();
local.adcs.1.start();
local.dacs.0.start();
local.dacs.1.start();
settings_update::spawn().unwrap();
telemetry::spawn().unwrap();
ethernet_link::spawn().unwrap();
usb::spawn().unwrap();
start::spawn().unwrap();
(shared, local)
}
#[task(priority = 1, local=[sampling_timer])]
async fn start(c: start::Context) {
Systick::delay(100.millis()).await;
c.local.sampling_timer.start();
}
#[task(binds=DMA1_STR4, local=[digital_inputs, adcs, dacs, iir_state, generator], shared=[active_settings, source, telemetry], priority=3)]
#[link_section = ".itcm.process"]
fn process(c: process::Context) {
let process::SharedResources {
active_settings,
telemetry,
source,
..
} = c.shared;
let process::LocalResources {
digital_inputs,
adcs: (adc0, adc1),
dacs: (dac0, dac1),
iir_state,
generator,
..
} = c.local;
(active_settings, telemetry, source).lock(
|settings, telemetry, source| {
let digital_inputs =
[digital_inputs.0.is_high(), digital_inputs.1.is_high()];
telemetry.digital_inputs = digital_inputs;
let hold = *settings.force_hold
|| (digital_inputs[1] && *settings.allow_hold);
(adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| {
let adc_samples = [adc0, adc1];
let dac_samples = [dac0, dac1];
fence(Ordering::SeqCst);
for channel in 0..adc_samples.len() {
adc_samples[channel]
.iter()
.zip(dac_samples[channel].iter_mut())
.zip(&mut source[channel])
.map(|((ai, di), signal)| {
let x = f32::from(*ai as i16);
let y = settings.iir_ch[channel]
.iter()
.zip(iir_state[channel].iter_mut())
.fold(x, |yi, (ch, state)| {
let filter = if hold {
&iir::Biquad::HOLD
} else {
ch
};
filter.update(state, yi)
});
let y: i16 = unsafe { y.to_int_unchecked() };
let y = y.saturating_add(signal);
*di = DacCode::from(y).0;
})
.last();
}
const N: usize = BATCH_SIZE * size_of::<i16>();
generator.add(|buf| {
for (data, buf) in adc_samples
.iter()
.chain(dac_samples.iter())
.zip(buf.chunks_exact_mut(N))
{
let data = unsafe {
core::slice::from_raw_parts(
data.as_ptr() as *const MaybeUninit<u8>,
N,
)
};
buf.copy_from_slice(data)
}
N * 4
});
telemetry.adcs = [
AdcCode(adc_samples[0][0]),
AdcCode(adc_samples[1][0]),
];
telemetry.dacs = [
DacCode(dac_samples[0][0]),
DacCode(dac_samples[1][0]),
];
fence(Ordering::SeqCst);
});
},
);
}
#[idle(shared=[network, settings, usb])]
fn idle(mut c: idle::Context) -> ! {
loop {
match (&mut c.shared.network, &mut c.shared.settings)
.lock(|net, settings| net.update(&mut settings.dual_iir))
{
NetworkState::SettingsChanged => {
settings_update::spawn().unwrap()
}
NetworkState::Updated => {}
NetworkState::NoChange => {
if c.shared.usb.lock(|usb| {
usb.state()
== usb_device::device::UsbDeviceState::Suspend
}) {
cortex_m::asm::wfi();
}
}
}
}
}
#[task(priority = 1, local=[afes], shared=[network, settings, active_settings, source])]
async fn settings_update(mut c: settings_update::Context) {
c.shared.settings.lock(|settings| {
c.local.afes.0.set_gain(*settings.dual_iir.afe[0]);
c.local.afes.1.set_gain(*settings.dual_iir.afe[1]);
for (i, &config) in settings.dual_iir.source.iter().enumerate() {
match config.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE)
{
Ok(config) => {
c.shared.source.lock(|generator| {
generator[i].update_waveform(config)
});
}
Err(err) => log::error!(
"Failed to update signal generation on DAC{}: {:?}",
i,
err
),
}
}
c.shared
.network
.lock(|net| net.direct_stream(*settings.dual_iir.stream));
c.shared
.active_settings
.lock(|current| *current = settings.dual_iir.clone());
});
}
#[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])]
async fn telemetry(mut c: telemetry::Context) {
loop {
let telemetry =
c.shared.telemetry.lock(|telemetry| telemetry.clone());
let (gains, telemetry_period) =
c.shared.settings.lock(|settings| {
(settings.dual_iir.afe, *settings.dual_iir.telemetry_period)
});
c.shared.network.lock(|net| {
net.telemetry.publish(&telemetry.finalize(
*gains[0],
*gains[1],
c.local.cpu_temp_sensor.get_temperature().unwrap(),
))
});
Systick::delay((telemetry_period as u32).secs()).await;
}
}
#[task(priority = 1, shared=[usb, settings], local=[usb_terminal])]
async fn usb(mut c: usb::Context) {
loop {
c.shared.usb.lock(|usb| {
usb.poll(&mut [c
.local
.usb_terminal
.interface_mut()
.inner_mut()]);
});
c.shared.settings.lock(|settings| {
if c.local.usb_terminal.poll(settings).unwrap() {
settings_update::spawn().unwrap()
}
});
Systick::delay(10.millis()).await;
}
}
#[task(priority = 1, shared=[network])]
async fn ethernet_link(mut c: ethernet_link::Context) {
loop {
c.shared.network.lock(|net| net.processor.handle_link());
Systick::delay(1.secs()).await;
}
}
#[task(binds = ETH, priority = 1)]
fn eth(_: eth::Context) {
unsafe { hal::ethernet::interrupt_handler() }
}
#[task(binds = SPI2, priority = 4)]
fn spi2(_: spi2::Context) {
panic!("ADC0 SPI error");
}
#[task(binds = SPI3, priority = 4)]
fn spi3(_: spi3::Context) {
panic!("ADC1 SPI error");
}
#[task(binds = SPI4, priority = 4)]
fn spi4(_: spi4::Context) {
panic!("DAC0 SPI error");
}
#[task(binds = SPI5, priority = 4)]
fn spi5(_: spi5::Context) {
panic!("DAC1 SPI error");
}
}