422: Updating Stabilizer to remove compile-time config parameters r=ryan-summers a=ryan-summers

This PR removes `src/configuration.rs` in favor of application-level constants. It also updates the MQTT broker IP to be specified via the command-line during compilation, and will default to 10.34.16.10

This should pave the way to storing some of these parameters potentially in flash for loading on boot.

This fixes #417

Co-authored-by: Ryan Summers <ryan.summers@vertigo-designs.com>
master
bors[bot] 2021-08-02 14:50:20 +00:00 committed by GitHub
commit f1da472d84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 187 additions and 162 deletions

View File

@ -17,7 +17,6 @@ nav_order: 2
There are a number of steps that must be completed when first getting started with Stabilizer.
1. Update the Stabilizer Application
1. Set parameters in the firmware source code, such as IP addresses and sampling rate.
1. Build the application by compiling the source code.
1. Upload the application and programming it onto the device.
1. Set up MQTT for telemetry and configuration.
@ -31,23 +30,6 @@ Firmware is compiled and loaded onto Stabilizer to program a specific applicatio
After receiving the Stabilizer hardware, you will need to flash firmware onto the device to use your
desired application.
## Configuring Firmware
Stabilizer firmware contains compile-time parameters that may need to be changed based on
application requirements. Some examples of parameters that may require configuraton:
* Sampling interval.
* Batch size.
* MQTT Broker IP address
Parameters are configured by editing the file `src/configuration.rs`.
Refer to the [documentation]({{site.baseurl}}/firmware/stabilizer/configuration/index.html) for more
information on parameters.
When these parameters are updated, firmware must be built and flashed onto Stabilizer before they
take effect.
## Building Firmware
1. Clone or download [stabilizer](https://github.com/quartiq/stabilizer)
@ -60,9 +42,9 @@ git clone https://github.com/quartiq/stabilizer
```bash
rustup target add thumbv7em-none-eabihf
```
1. Build Firmware
1. Build Firmware with an optionally-specified MQTT broker IP.
```bash
cargo build --release
BROKER="10.34.16.10" cargo build --release
```
## Uploading Firmware
@ -133,7 +115,7 @@ been used during development, but any MQTTv5 broker is supported.
> later is used.
Stabilizer utilizes a static IP address for broker configuration. Ensure the IP address was
[configured](#configuring-firmware) properly to point to your broker before continuing.
[configured](#building-firmware) properly to point to your broker before continuing.
## Test the Connection
Once your broker is running, test that Stabilizer is properly connected to it.

View File

@ -29,10 +29,7 @@
#![no_std]
#![no_main]
use core::{
convert::TryInto,
sync::atomic::{fence, Ordering},
};
use core::sync::atomic::{fence, Ordering};
use mutex_trait::prelude::*;
@ -43,7 +40,6 @@ use stabilizer::{
adc::{Adc0Input, Adc1Input, AdcCode},
afe::Gain,
dac::{Dac0Output, Dac1Output, DacCode},
design_parameters::SAMPLE_BUFFER_SIZE,
embedded_hal::digital::v2::InputPin,
hal,
signal_generator::{self, SignalGenerator},
@ -51,6 +47,7 @@ use stabilizer::{
DigitalInput0, DigitalInput1, AFE0, AFE1,
},
net::{
self,
data_stream::{FrameGenerator, StreamFormat, StreamTarget},
miniconf::Miniconf,
serde::Deserialize,
@ -64,6 +61,13 @@ const SCALE: f32 = i16::MAX as _;
// The number of cascaded IIR biquads per channel. Select 1 or 2!
const IIR_CASCADE_LENGTH: usize = 1;
// The number of samples in each batch process
const BATCH_SIZE: usize = 8;
// The logarithm of the number of 100MHz timer ticks between each sample. With a value of 2^7 =
// 128, there is 1.28uS per sample, corresponding to a sampling frequency of 781.25 KHz.
const SAMPLE_TICKS_LOG2: u8 = 7;
#[derive(Clone, Copy, Debug, Deserialize, Miniconf)]
pub struct Settings {
/// Configure the Analog Front End (AFE) gain.
@ -183,8 +187,12 @@ const APP: () = {
#[init(spawn=[telemetry, settings_update, ethernet_link])]
fn init(c: init::Context) -> init::LateResources {
// Configure the microcontroller
let (mut stabilizer, _pounder) =
hardware::setup::setup(c.core, c.device);
let (mut stabilizer, _pounder) = hardware::setup::setup(
c.core,
c.device,
BATCH_SIZE,
1 << SAMPLE_TICKS_LOG2,
);
let mut network = NetworkUsers::new(
stabilizer.net.stack,
@ -192,12 +200,11 @@ const APP: () = {
stabilizer.cycle_counter,
env!("CARGO_BIN_NAME"),
stabilizer.net.mac_address,
net::parse_or_default_broker(option_env!("BROKER")),
);
let generator = network.configure_streaming(
StreamFormat::AdcDacData,
SAMPLE_BUFFER_SIZE as u8,
);
let generator = network
.configure_streaming(StreamFormat::AdcDacData, BATCH_SIZE as u8);
// Spawn a settings update for default settings.
c.spawn.settings_update().unwrap();
@ -228,10 +235,14 @@ const APP: () = {
settings,
signal_generator: [
SignalGenerator::new(
settings.signal_generator[0].try_into().unwrap(),
settings.signal_generator[0]
.try_into_config(SAMPLE_TICKS_LOG2)
.unwrap(),
),
SignalGenerator::new(
settings.signal_generator[1].try_into().unwrap(),
settings.signal_generator[1]
.try_into_config(SAMPLE_TICKS_LOG2)
.unwrap(),
),
],
}
@ -311,14 +322,13 @@ const APP: () = {
}
// Stream the data.
const N: usize = SAMPLE_BUFFER_SIZE * core::mem::size_of::<u16>();
const N: usize = BATCH_SIZE * core::mem::size_of::<u16>();
generator.add::<_, { N * 4 }>(|buf| {
for (data, buf) in adc_samples
.iter()
.chain(dac_samples.iter())
.zip(buf.chunks_exact_mut(N))
{
assert_eq!(core::mem::size_of_val(*data), N);
let data = unsafe {
core::slice::from_raw_parts(
data.as_ptr() as *const u8,
@ -366,7 +376,7 @@ const APP: () = {
// Update the signal generators
for (i, &config) in settings.signal_generator.iter().enumerate() {
match config.try_into() {
match config.try_into_config(SAMPLE_TICKS_LOG2) {
Ok(config) => {
c.resources
.signal_generator

View File

@ -37,13 +37,11 @@ use mutex_trait::prelude::*;
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
use stabilizer::{
configuration,
hardware::{
self,
adc::{Adc0Input, Adc1Input, AdcCode},
afe::Gain,
dac::{Dac0Output, Dac1Output, DacCode},
design_parameters::SAMPLE_BUFFER_SIZE,
embedded_hal::digital::v2::InputPin,
hal,
input_stamper::InputStamper,
@ -52,6 +50,7 @@ use stabilizer::{
DigitalInput0, DigitalInput1, AFE0, AFE1,
},
net::{
self,
data_stream::{FrameGenerator, StreamFormat, StreamTarget},
miniconf::Miniconf,
serde::Deserialize,
@ -60,6 +59,15 @@ use stabilizer::{
},
};
// The logarithm of the number of samples in each batch process. This corresponds with 2^3 samples
// per batch = 8 samples
const BATCH_SIZE_SIZE_LOG2: u8 = 3;
// The logarithm of the number of 100MHz timer ticks between each sample. This corresponds with a
// sampling period of 2^7 = 128 ticks. At 100MHz, 10ns per tick, this corresponds to a sampling
// period of 1.28 uS or 781.25 KHz.
const ADC_SAMPLE_TICKS_LOG2: u8 = 7;
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
enum Conf {
/// Output the lockin magnitude.
@ -220,8 +228,12 @@ const APP: () = {
#[init(spawn=[settings_update, telemetry, ethernet_link])]
fn init(c: init::Context) -> init::LateResources {
// Configure the microcontroller
let (mut stabilizer, _pounder) =
hardware::setup::setup(c.core, c.device);
let (mut stabilizer, _pounder) = hardware::setup::setup(
c.core,
c.device,
1 << BATCH_SIZE_SIZE_LOG2,
1 << ADC_SAMPLE_TICKS_LOG2,
);
let mut network = NetworkUsers::new(
stabilizer.net.stack,
@ -229,19 +241,17 @@ const APP: () = {
stabilizer.cycle_counter,
env!("CARGO_BIN_NAME"),
stabilizer.net.mac_address,
net::parse_or_default_broker(option_env!("BROKER")),
);
let generator = network.configure_streaming(
StreamFormat::AdcDacData,
SAMPLE_BUFFER_SIZE as u8,
1u8 << BATCH_SIZE_SIZE_LOG2,
);
let settings = Settings::default();
let pll = RPLL::new(
configuration::ADC_SAMPLE_TICKS_LOG2
+ configuration::SAMPLE_BUFFER_SIZE_LOG2,
);
let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + BATCH_SIZE_SIZE_LOG2);
// Spawn a settings and telemetry update for default settings.
c.spawn.settings_update().unwrap();
@ -267,7 +277,7 @@ const APP: () = {
let signal_config = {
let frequency_tuning_word =
(1u64 << (32 - configuration::SAMPLE_BUFFER_SIZE_LOG2)) as u32;
(1u64 << (32 - BATCH_SIZE_SIZE_LOG2)) as u32;
signal_generator::Config {
// Same frequency as batch size.
@ -334,18 +344,11 @@ const APP: () = {
settings.pll_tc[0],
settings.pll_tc[1],
);
(
pll_phase,
(pll_frequency >> configuration::SAMPLE_BUFFER_SIZE_LOG2)
as i32,
)
(pll_phase, (pll_frequency >> BATCH_SIZE_SIZE_LOG2) as i32)
}
LockinMode::Internal => {
// Reference phase and frequency are known.
(
1i32 << 30,
1i32 << (32 - configuration::SAMPLE_BUFFER_SIZE_LOG2),
)
(1i32 << 30, 1i32 << (32 - BATCH_SIZE_SIZE_LOG2))
}
};
@ -399,14 +402,14 @@ const APP: () = {
}
// Stream the data.
const N: usize = SAMPLE_BUFFER_SIZE * core::mem::size_of::<u16>();
const N: usize =
(1 << BATCH_SIZE_SIZE_LOG2) * core::mem::size_of::<u16>();
generator.add::<_, { N * 4 }>(|buf| {
for (data, buf) in adc_samples
.iter()
.chain(dac_samples.iter())
.zip(buf.chunks_exact_mut(N))
{
assert_eq!(core::mem::size_of_val(*data), N);
let data = unsafe {
core::slice::from_raw_parts(
data.as_ptr() as *const u8,

View File

@ -1,41 +0,0 @@
//! This module contains any compile-time configuration parameters for the Stabilizer firmware.
/// MQTT broker IPv4 address
///
/// In the default configuration, the IP address is defined as 10.34.16.10.
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
/// Sampling Frequency
///
/// Define the frequency at which ADCs (and DACs) are sampled at.
///
/// # Units
/// The units of this parameter are specified as a logarithmic number of ticks of the internal
/// timer, which runs at 100 MHz.
///
/// ## Example
/// With a value of 7, this corresponds to 2^7 = 128 ticks. Each tick of the 100 MHz timer requires
/// 10ns.
///
/// Sampling Period = 10ns * 128 = 1.28 us
/// Sampling Frequency = 781.25 KHz
///
/// Or more succinctly:
/// `F_s = 100 MHz / (2 ^ ADC_SAMPLE_TICKS_LOG2)`
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7;
/// Sample Batch Sizing
///
/// The sample batch size defines how many samples are collected before the DSP routines are
/// executed.
///
/// # Note
/// Smaller batch sizes result in less input -> output latency, but come at the cost of reduced
/// maximum sampling frequency.
///
/// # Units
/// The units of the batch size are specified logarithmically.
///
/// ## Example
/// With a value of 3, the number of samples per batch is 2^3 = 8.
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;

View File

@ -69,7 +69,7 @@ use stm32h7xx_hal as hal;
use mutex_trait::Mutex;
use super::design_parameters::{SampleBuffer, SAMPLE_BUFFER_SIZE};
use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
use super::timers;
use hal::dma::{
@ -142,7 +142,8 @@ static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
#[link_section = ".axisram.buffers"]
static mut ADC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
static mut ADC_BUF: [[SampleBuffer; 2]; 2] =
[[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
macro_rules! adc_input {
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
@ -218,7 +219,7 @@ macro_rules! adc_input {
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
PeripheralToMemory,
&'static mut SampleBuffer,
&'static mut [u16],
hal::dma::DBTransfer,
>,
trigger_transfer: Transfer<
@ -258,6 +259,7 @@ macro_rules! adc_input {
clear_stream: hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
trigger_channel: timers::tim2::$trigger_channel,
clear_channel: timers::tim3::$clear_channel,
batch_size: usize,
) -> Self {
// The flag clear DMA transfer always clears the EOT flag in the SPI
// peripheral. It has the highest priority to ensure it is completed before the
@ -357,8 +359,8 @@ macro_rules! adc_input {
spi,
// Note(unsafe): The ADC_BUF[$index] is "owned" by this peripheral.
// It shall not be used anywhere else in the module.
unsafe { &mut ADC_BUF[$index][0] },
unsafe { Some(&mut ADC_BUF[$index][1]) },
unsafe { &mut ADC_BUF[$index][0][..batch_size] },
unsafe { Some(&mut ADC_BUF[$index][1][..batch_size]) },
data_config,
);
@ -390,8 +392,8 @@ macro_rules! adc_input {
/// NOTE(unsafe): Memory safety and access ordering is not guaranteed
/// (see the HAL DMA docs).
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
where
F: FnOnce(&mut SampleBuffer) -> R,
where
F: FnOnce(&mut &'static mut [u16]) -> R,
{
unsafe { self.transfer.next_dbm_transfer_with(|buf, _current| f(buf)) }
}
@ -400,7 +402,7 @@ macro_rules! adc_input {
// This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
// to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
impl Mutex for $name {
type Data = SampleBuffer;
type Data = &'static mut [u16];
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
self.with_buffer(f).unwrap()
}

View File

@ -54,7 +54,7 @@ use stm32h7xx_hal as hal;
use mutex_trait::Mutex;
use super::design_parameters::{SampleBuffer, SAMPLE_BUFFER_SIZE};
use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
use super::timers;
use core::convert::TryFrom;
@ -70,7 +70,8 @@ use hal::dma::{
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
#[link_section = ".axisram.buffers"]
static mut DAC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
static mut DAC_BUF: [[SampleBuffer; 2]; 2] =
[[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
/// Custom type for referencing DAC output codes.
/// The internal integer is the raw code written to the DAC output register.
@ -176,7 +177,7 @@ macro_rules! dac_output {
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
$spi,
MemoryToPeripheral,
&'static mut SampleBuffer,
&'static mut [u16],
hal::dma::DBTransfer,
>,
}
@ -192,6 +193,7 @@ macro_rules! dac_output {
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
trigger_channel: timers::tim2::$trigger_channel,
batch_size: usize,
) -> Self {
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
// occurs.
@ -225,9 +227,9 @@ macro_rules! dac_output {
stream,
$spi::new(trigger_channel, spi),
// Note(unsafe): This buffer is only used once and provided for the DMA transfer.
unsafe { &mut DAC_BUF[$index][0] },
unsafe { &mut DAC_BUF[$index][0][..batch_size] },
// Note(unsafe): This buffer is only used once and provided for the DMA transfer.
unsafe { Some(&mut DAC_BUF[$index][1]) },
unsafe { Some(&mut DAC_BUF[$index][1][..batch_size]) },
trigger_config,
);
@ -246,7 +248,7 @@ macro_rules! dac_output {
/// (see the HAL DMA docs).
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
where
F: FnOnce(&mut SampleBuffer) -> R,
F: FnOnce(&mut &'static mut [u16]) -> R,
{
unsafe {
self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
@ -257,7 +259,7 @@ macro_rules! dac_output {
// This is not actually a Mutex. It only re-uses the semantics and macros of mutex-trait
// to reduce rightward drift when jointly calling `with_buffer(f)` on multiple DAC/ADCs.
impl Mutex for $name {
type Data = SampleBuffer;
type Data = &'static mut [u16];
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
self.with_buffer(f).unwrap()
}

View File

@ -40,13 +40,7 @@ pub const DDS_SYSTEM_CLK: MegaHertz =
#[allow(dead_code)]
pub const DDS_SYNC_CLK_DIV: u8 = 4;
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
// equal to 10ns per tick.
pub const ADC_SAMPLE_TICKS: u16 =
1 << crate::configuration::ADC_SAMPLE_TICKS_LOG2;
/// The maximum ADC/DAC sample processing buffer size.
pub const MAX_SAMPLE_BUFFER_SIZE: usize = 32;
// The desired ADC sample processing buffer size.
pub const SAMPLE_BUFFER_SIZE: usize =
1 << crate::configuration::SAMPLE_BUFFER_SIZE_LOG2;
pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE];
pub type SampleBuffer = [u16; MAX_SAMPLE_BUFFER_SIZE];

View File

@ -16,8 +16,7 @@
///! capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the
///! batch size. This results in the input capture triggering identically to when the ADC samples
///! the last sample of the batch. That sample is then available for processing by the user.
use crate::{configuration, hardware::timers};
use core::convert::TryFrom;
use crate::hardware::timers;
use stm32h7xx_hal as hal;
/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
@ -34,6 +33,7 @@ impl Timestamper {
/// * `capture_channel` - The input capture channel for collecting timestamps.
/// * `sampling_timer` - The stabilizer ADC sampling timer.
/// * `_clock_input` - The input pin for the external clock from Pounder.
/// * `batch_size` - The number of samples in each batch.
///
/// # Returns
/// The new pounder timestamper in an operational state.
@ -44,6 +44,7 @@ impl Timestamper {
_clock_input: hal::gpio::gpioa::PA0<
hal::gpio::Alternate<hal::gpio::AF3>,
>,
batch_size: usize,
) -> Self {
// The sampling timer should generate a trigger output when CH1 comparison occurs.
sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
@ -56,11 +57,16 @@ impl Timestamper {
let mut input_capture = capture_channel
.into_input_capture(timers::tim8::CaptureSource1::TRC);
let prescaler = match batch_size {
1 => timers::Prescaler::Div1,
2 => timers::Prescaler::Div2,
4 => timers::Prescaler::Div4,
8 => timers::Prescaler::Div8,
_ => panic!("Batch size does not support DDS timestamping"),
};
// Capture at the batch period.
input_capture.configure_prescaler(
timers::Prescaler::try_from(configuration::SAMPLE_BUFFER_SIZE_LOG2)
.unwrap(),
);
input_capture.configure_prescaler(prescaler);
Self {
timer: timestamp_timer,

View File

@ -172,9 +172,14 @@ fn load_itcm() {
/// Configure the stabilizer hardware for operation.
///
/// # Note
/// Refer to [design_parameters::TIMER_FREQUENCY] to determine the frequency of the sampling timer.
///
/// # Args
/// * `core` - The RTIC core for configuring the cortex-M core of the device.
/// * `device` - The microcontroller peripherals to be configured.
/// * `batch_size` - The size of each ADC/DAC batch.
/// * `sample_ticks` - The number of timer ticks between each sample.
///
/// # Returns
/// (stabilizer, pounder) where `stabilizer` is a `StabilizerDevices` structure containing all
@ -184,6 +189,8 @@ fn load_itcm() {
pub fn setup(
mut core: rtic::Peripherals,
device: stm32h7xx_hal::stm32::Peripherals,
batch_size: usize,
sample_ticks: u32,
) -> (StabilizerDevices, Option<PounderDevices>) {
let pwr = device.PWR.constrain();
let vos = pwr.freeze();
@ -295,8 +302,7 @@ pub fn setup(
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
let mut sampling_timer = timers::SamplingTimer::new(timer2);
sampling_timer
.set_period_ticks((design_parameters::ADC_SAMPLE_TICKS - 1) as u32);
sampling_timer.set_period_ticks(sample_ticks - 1);
// The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
// it generates a trigger whenever it is enabled.
@ -320,8 +326,7 @@ pub fn setup(
let mut shadow_sampling_timer =
timers::ShadowSamplingTimer::new(timer3);
shadow_sampling_timer
.set_period_ticks(design_parameters::ADC_SAMPLE_TICKS - 1);
shadow_sampling_timer.set_period_ticks(sample_ticks as u16 - 1);
// The shadow sampling timer is a slave-mode timer to the sampling timer. It should
// always be in-sync - thus, we configure it to operate in slave mode using "Trigger
@ -409,6 +414,7 @@ pub fn setup(
dma_streams.2,
sampling_timer_channels.ch1,
shadow_sampling_timer_channels.ch1,
batch_size,
)
};
@ -452,6 +458,7 @@ pub fn setup(
dma_streams.5,
sampling_timer_channels.ch2,
shadow_sampling_timer_channels.ch2,
batch_size,
)
};
@ -539,11 +546,13 @@ pub fn setup(
dac0_spi,
dma_streams.6,
sampling_timer_channels.ch3,
batch_size,
);
let dac1 = dac::Dac1Output::new(
dac1_spi,
dma_streams.7,
sampling_timer_channels.ch4,
batch_size,
);
(dac0, dac1)
};
@ -940,8 +949,7 @@ pub fn setup(
let sample_frequency = {
let timer_frequency: hal::time::Hertz =
design_parameters::TIMER_FREQUENCY.into();
timer_frequency.0 as f32
/ design_parameters::ADC_SAMPLE_TICKS as f32
timer_frequency.0 as f32 / sample_ticks as f32
};
let sample_period = 1.0 / sample_frequency;
@ -986,6 +994,7 @@ pub fn setup(
tim8_channels.ch1,
&mut sampling_timer,
etr_pin,
batch_size,
)
};

View File

@ -1,8 +1,7 @@
use crate::{
configuration::ADC_SAMPLE_TICKS_LOG2, hardware::dac::DacCode,
hardware::design_parameters::TIMER_FREQUENCY,
hardware::dac::DacCode, hardware::design_parameters::TIMER_FREQUENCY,
};
use core::convert::{TryFrom, TryInto};
use core::convert::TryFrom;
use miniconf::Miniconf;
use serde::Deserialize;
@ -32,7 +31,7 @@ pub struct BasicConfig {
/// The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal.
/// At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this
//// symmetry is the duty cycle.
/// symmetry is the duty cycle.
pub symmetry: f32,
/// The amplitude of the output signal in volts.
@ -61,20 +60,25 @@ pub enum Error {
InvalidFrequency,
}
impl TryFrom<BasicConfig> for Config {
type Error = Error;
fn try_from(config: BasicConfig) -> Result<Config, Error> {
let symmetry_complement = 1.0 - config.symmetry;
impl BasicConfig {
/// Convert configuration into signal generator values.
///
/// # Args
/// * `sample_ticks_log2` - The logarithm of the number of timer sample ticks between each
/// sample.
pub fn try_into_config(
self,
sample_ticks_log2: u8,
) -> Result<Config, Error> {
let symmetry_complement = 1.0 - self.symmetry;
// Validate symmetry
if config.symmetry < 0.0 || symmetry_complement < 0.0 {
if self.symmetry < 0.0 || symmetry_complement < 0.0 {
return Err(Error::InvalidSymmetry);
}
const LSB_PER_HERTZ: f32 = (1u64 << (31 + ADC_SAMPLE_TICKS_LOG2))
as f32
let lsb_per_hertz: f32 = (1u64 << (31 + sample_ticks_log2)) as f32
/ (TIMER_FREQUENCY.0 * 1_000_000) as f32;
let ftw = config.frequency * LSB_PER_HERTZ;
let ftw = self.frequency * lsb_per_hertz;
// Validate base frequency tuning word to be below Nyquist.
const NYQUIST: f32 = (1u32 << 31) as _;
@ -85,8 +89,8 @@ impl TryFrom<BasicConfig> for Config {
// Calculate the frequency tuning words.
// Clip both frequency tuning words to within Nyquist before rounding.
let frequency_tuning_word = [
if config.symmetry * NYQUIST > ftw {
ftw / config.symmetry
if self.symmetry * NYQUIST > ftw {
ftw / self.symmetry
} else {
NYQUIST
} as u32,
@ -98,10 +102,10 @@ impl TryFrom<BasicConfig> for Config {
];
Ok(Config {
amplitude: DacCode::try_from(config.amplitude)
amplitude: DacCode::try_from(self.amplitude)
.or(Err(Error::InvalidAmplitude))?
.into(),
signal: config.signal,
signal: self.signal,
frequency_tuning_word,
})
}
@ -119,6 +123,16 @@ pub struct Config {
pub frequency_tuning_word: [u32; 2],
}
impl Default for Config {
fn default() -> Self {
Self {
signal: Signal::Cosine,
amplitude: 0,
frequency_tuning_word: [0, 0],
}
}
}
#[derive(Debug)]
pub struct SignalGenerator {
phase_accumulator: u32,
@ -128,7 +142,7 @@ pub struct SignalGenerator {
impl Default for SignalGenerator {
fn default() -> Self {
Self {
config: BasicConfig::default().try_into().unwrap(),
config: Config::default(),
phase_accumulator: 0,
}
}

View File

@ -1,6 +1,5 @@
#![no_std]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
pub mod configuration;
pub mod hardware;
pub mod net;

View File

@ -14,7 +14,7 @@ use heapless::String;
use log::info;
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
use crate::configuration::MQTT_BROKER;
use minimq::embedded_nal::IpAddr;
/// MQTT settings interface.
pub struct MiniconfClient<S>
@ -38,9 +38,14 @@ where
/// * `stack` - The network stack to use for communication.
/// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning.
/// * `prefix` - The MQTT device prefix to use for this device.
pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self {
let mqtt =
minimq::Minimq::new(MQTT_BROKER.into(), client_id, stack).unwrap();
/// * `broker` - The IP address of the MQTT broker to use.
pub fn new(
stack: NetworkReference,
client_id: &str,
prefix: &str,
broker: IpAddr,
) -> Self {
let mqtt = minimq::Minimq::new(broker, client_id, stack).unwrap();
let mut response_topic: String<128> = String::from(prefix);
response_topic.push_str("/log").unwrap();

View File

@ -20,6 +20,7 @@ use crate::hardware::{cycle_counter::CycleCounter, EthernetPhy, NetworkStack};
use data_stream::{DataStream, FrameGenerator};
use messages::{MqttMessage, SettingsResponse};
use miniconf_client::MiniconfClient;
use minimq::embedded_nal::IpAddr;
use network_processor::NetworkProcessor;
use shared::NetworkManager;
use telemetry::TelemetryClient;
@ -32,6 +33,9 @@ use smoltcp_nal::embedded_nal::SocketAddr;
pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>;
/// The default MQTT broker IP address if unspecified.
pub const DEFAULT_MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
#[derive(Copy, Clone, PartialEq)]
pub enum UpdateState {
NoChange,
@ -66,6 +70,7 @@ where
/// * `cycle_counter` - The clock used for measuring time in the network.
/// * `app` - The name of the application.
/// * `mac` - The MAC address of the network.
/// * `broker` - The IP address of the MQTT broker to use.
///
/// # Returns
/// A new struct of network users.
@ -75,6 +80,7 @@ where
cycle_counter: CycleCounter,
app: &str,
mac: smoltcp_nal::smoltcp::wire::EthernetAddress,
broker: IpAddr,
) -> Self {
let stack_manager =
cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack))
@ -92,12 +98,14 @@ where
stack_manager.acquire_stack(),
&get_client_id(app, "settings", mac),
&prefix,
broker,
);
let telemetry = TelemetryClient::new(
stack_manager.acquire_stack(),
&get_client_id(app, "tlm", mac),
&prefix,
broker,
);
let (generator, stream) =
@ -200,3 +208,30 @@ pub fn get_device_prefix(
prefix
}
/// Determine the broker IP address
///
/// # Note
/// If the broker IP is unspecified or unparseable, the default IP is returned.
///
/// # Args
/// * `input` - The optionally-specified command-line broker IP address as a string.
///
/// # Returns
/// The broker IP address.
pub fn parse_or_default_broker(input: Option<&str>) -> IpAddr {
input.and_then(|data| {
data.parse::<minimq::embedded_nal::IpAddr>().map_or_else(
|err| {
log::error!(
"{:?}: Failed to parse broker IP ({:?}) - Falling back to default",
err,
data
);
None
},
Some,
)
})
.unwrap_or_else(|| DEFAULT_MQTT_BROKER.into())
}

View File

@ -15,8 +15,8 @@ use minimq::QoS;
use serde::Serialize;
use super::NetworkReference;
use crate::configuration::MQTT_BROKER;
use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode};
use minimq::embedded_nal::IpAddr;
/// The telemetry client for reporting telemetry data over MQTT.
pub struct TelemetryClient<T: Serialize> {
@ -96,12 +96,17 @@ impl<T: Serialize> TelemetryClient<T> {
/// * `stack` - A reference to the (shared) underlying network stack.
/// * `client_id` - The MQTT client ID of the telemetry client.
/// * `prefix` - The device prefix to use for MQTT telemetry reporting.
/// * `broker` - The IP address of the MQTT broker to use.
///
/// # Returns
/// A new telemetry client.
pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self {
let mqtt =
minimq::Minimq::new(MQTT_BROKER.into(), client_id, stack).unwrap();
pub fn new(
stack: NetworkReference,
client_id: &str,
prefix: &str,
broker: IpAddr,
) -> Self {
let mqtt = minimq::Minimq::new(broker, client_id, stack).unwrap();
let mut telemetry_topic: String<128> = String::from(prefix);
telemetry_topic.push_str("/telemetry").unwrap();