Merge #422
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>
This commit is contained in:
commit
f1da472d84
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
||||
|
||||
pub mod configuration;
|
||||
pub mod hardware;
|
||||
pub mod net;
|
||||
|
@ -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();
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user