Squashing manual changes

This commit is contained in:
Ryan Summers 2021-07-15 13:28:19 +02:00
parent abe810f8d6
commit f1947db6c9
34 changed files with 875 additions and 132 deletions

View File

@ -83,3 +83,29 @@ jobs:
with: with:
command: bench command: bench
args: --package dsp --target=x86_64-unknown-linux-gnu args: --package dsp --target=x86_64-unknown-linux-gnu
doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: thumbv7em-none-eabihf
override: true
- name: Install Deadlinks
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-deadlinks
- name: cargo doc
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps -p miniconf -p dsp -p ad9959 -p stabilizer
- name: cargo deadlinks
uses: actions-rs/cargo@v1
with:
command: deadlinks
args: --dir target/thumbv7em-none-eabihf/doc

35
.github/workflows/release-docs.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Release Documentation
on:
workflow_dispatch:
push:
# branches:
# - master
jobs:
release-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: thumbv7em-none-eabihf
override: true
- uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps -p miniconf -p ad9959 -p stabilizer -p dsp
- run: mv target/thumbv7em-none-eabihf/doc docs/firmware
- uses: peaceiris/actions-gh-pages@v3.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs
enable_jekyll: true
publish_branch: pages

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
docs/_site/

4
Cargo.lock generated
View File

@ -200,7 +200,7 @@ dependencies = [
[[package]] [[package]]
name = "derive_miniconf" name = "derive_miniconf"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/quartiq/miniconf.git?rev=2750533#275053396f0334e9efefa1ab2aae4c19b95a9a53" source = "git+https://github.com/quartiq/miniconf.git?rev=9c826f8#9c826f8de8d0dd1a59e1ce7bf124ac0311994b46"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -414,7 +414,7 @@ dependencies = [
[[package]] [[package]]
name = "miniconf" name = "miniconf"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/quartiq/miniconf.git?rev=2750533#275053396f0334e9efefa1ab2aae4c19b95a9a53" source = "git+https://github.com/quartiq/miniconf.git?rev=9c826f8#9c826f8de8d0dd1a59e1ce7bf124ac0311994b46"
dependencies = [ dependencies = [
"derive_miniconf", "derive_miniconf",
"serde", "serde",

View File

@ -70,7 +70,7 @@ rev = "a2e3ad5"
[patch.crates-io.miniconf] [patch.crates-io.miniconf]
git = "https://github.com/quartiq/miniconf.git" git = "https://github.com/quartiq/miniconf.git"
rev = "2750533" rev = "9c826f8"
[dependencies.smoltcp-nal] [dependencies.smoltcp-nal]
git = "https://github.com/quartiq/smoltcp-nal.git" git = "https://github.com/quartiq/smoltcp-nal.git"

View File

@ -13,79 +13,6 @@
This firmware offers a library of hardware and software functionality targeting the use of the Stabilizer hardware in various digital signal processing applications commonly occurring in Quantum Technology. This firmware offers a library of hardware and software functionality targeting the use of the Stabilizer hardware in various digital signal processing applications commonly occurring in Quantum Technology.
It provides abstractions over the fast analog inputs and outputs, time stamping, Pounder DDS interfaces and a collection of tailored and optimized digital signal processing algorithms (IIR, FIR, Lockin, PLL, reciprocal PLL, Unwrapper, Lowpass, Cosine-Sine, Atan2). It provides abstractions over the fast analog inputs and outputs, time stamping, Pounder DDS interfaces and a collection of tailored and optimized digital signal processing algorithms (IIR, FIR, Lockin, PLL, reciprocal PLL, Unwrapper, Lowpass, Cosine-Sine, Atan2).
An application can compose and configure these hardware and software components to implement different use cases. An application can compose and configure these hardware and software components to implement different use cases.
Several applications are provides by default:
### Dual-IIR Check out the [Documentation](https://quartiq.de/stabilizer) for more information on usage.
![Flow diagram](stabilizer_pid.svg)
* dual channel
* SPI ADC
* SPI DAC
* up to 800 kHz rate, timed sampling
* down to 2 µs latency
* f32 IIR math
* generic biquad (second order) IIR filter
* anti-windup
* derivative kick avoidance
### Lockin
* Up to 800 kHz sampling
* Up to 400 kHz modulation frequency
* Reciprocal PLL for external reference
* Internal reference
* Adjustable PLL and locking time constants
* Adjustable phase offset and harmonic index
* Different output modes (in-phase, quadrature, magnitude, log2 power, phase, frequency)
## Minimal bootstrapping documentation
* Clone or download this
* Get [rustup](https://rustup.rs/)
* Minimum supported Rust version (MSRV) is 1.52.0
* Install target support: `rustup target add thumbv7em-none-eabihf`
* Install `probe-run`: `cargo install probe-run`
* `cargo run --release --bin dual-iir`
* When using debug (non `--release`) mode, increase the sample interval significantly.
The added error checking code and missing optimizations may lead to the code
missing deadlines and panicing.
## Alternative flashing tools
### Cargo-embed
* Install `cargo-embed`: `cargo install cargo-embed`
* Program the device: `cargo embed --bin dual-iir --release`
### GDB/OpenOCD
* Get a recent openocd, a JTAG adapter ("st-link" or some clone) and
everything connected and permissions setup. Most
[Nucleo](https://www.digikey.de/short/p41h4v) boards have a
detachable ST-Link v2 and are cheap.[^swd]
* Get a multiarch `gdb` (or a cross arm gdb and edit `.cargo/config` accordingly)
* `openocd -f stabilizer.cfg` and leave it running
* `cargo run --release`
### USB-DFU
* Get [cargo-binutils](https://github.com/rust-embedded/cargo-binutils/)
* `cargo objcopy --release --bin dual-iir -- -O binary dual-iir.bin` or `arm-none-eabi-objcopy -O binary target/thumbv7em-none-eabihf/release/dual-iir dual-iir.bin`
* Install the DFU USB tool (`dfu-util`)
* Connect to the Micro USB connector below the RJ45
* Short JC2/BOOT
* `dfu-util -a 0 -s 0x08000000:leave -D dual-iir.bin`
### ST-Link virtual mass storage
* Prepare `dual-iir.bin` like above
* Connect the ST-Link debugger
* Copy `dual-iir.bin` to the `NODE_H743ZI` virtual mass storage device
## Protocol
Stabilizer can be configured via MQTT. Refer to
[`miniconf`](https://github.com/quartiq/miniconf) for more information about topics.
A basic command line interface is available in [`miniconf.py`](miniconf.py).
Telemetry is published via MQTT as well.

View File

3
docs/Gemfile Normal file
View File

@ -0,0 +1,3 @@
source 'https://rubygems.org'
gem "just-the-docs"
gem "jekyll-remote-theme"

81
docs/Gemfile.lock Normal file
View File

@ -0,0 +1,81 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
colorator (1.1.0)
concurrent-ruby (1.1.9)
em-websocket (0.5.2)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
eventmachine (1.2.7-x64-mingw32)
ffi (1.15.3-x64-mingw32)
forwardable-extended (2.6.0)
http_parser.rb (0.6.0)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
jekyll (4.2.0)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (~> 2.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (~> 0.4.0)
pathutil (~> 0.9)
rouge (~> 3.0)
safe_yaml (~> 1.0)
terminal-table (~> 2.0)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (2.1.0)
sassc (> 2.0.1, < 3.0)
jekyll-seo-tag (2.7.1)
jekyll (>= 3.8, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
just-the-docs (0.3.3)
jekyll (>= 3.8.5)
jekyll-seo-tag (~> 2.0)
rake (>= 12.3.1, < 13.1.0)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.5.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (3.26.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
sassc (2.4.0-x64-mingw32)
ffi (~> 1.9)
terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1)
unicode-display_width (1.7.0)
PLATFORMS
x64-mingw32
DEPENDENCIES
jekyll-remote-theme
just-the-docs
BUNDLED WITH
2.2.22

20
docs/README.md Normal file
View File

@ -0,0 +1,20 @@
This folder represents the Github Pages site that is used to host Stabilizer's user guide.
The site is hosted with Jekyll and utilizes the "Just the Docs" theme.
To run locally:
1. Install Ruby
1. Install [Jekyll](https://jekyllrb.com/)
1. Install [Bundler](https://bundler.io/)
1. From this directory:
```
bundle install
bundle exec jekyll serve
```
1. Navigate to `localhost:4000` in a web browser
Note: Some of the links in the docs rely on Cargo's documentation. To make all links work locally, run:
```
cargo doc --bins
cp -r target/thumbv7em-none-eabihf/doc docs/firmware
```

18
docs/_config.yml Normal file
View File

@ -0,0 +1,18 @@
remote_theme: pmarsceill/just-the-docs
title: Stabilizer
description: "User Manual"
logo: "/assets/stabilizer-logo.png"
url: "https://quartiq.de"
baseurl: "/stabilizer"
exclude: ['README.md']
plugins:
- jekyll-remote-theme
# Enable an auxilary link in top right with a new tab open
aux_links:
"Stabilizer on Github":
- "//github.com/quartiq/stabilizer"
aux_links_new_tab: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

55
docs/index.md Normal file
View File

@ -0,0 +1,55 @@
---
title: Home
layout: default
nav_order: 1
permalink: /
---
# Stabilizer
{: .no_toc }
## Table of Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
## Overview
Stabilizer is a flexible tool designed for quantum physics experiments. Fundamentally, Stabilizer
samples up two two analog input signals, performs digital signal processing internally, and then
generates up to two output signals.
Stabilizer firmware supports run-time configuration of the internal signal processing algorithms,
which allows for a wide variety of experimental uses, such as digital filter design or
implementation of digital lockin schemes.
This documentation is intended to bring a user up to speed on using Stabilizer and the firmware
provided by Quartiq.
## Hardware
The Stabilizer hardware is managed via a [separate repository](https://github.com/sinara-hw/Stabilizer).
[![Hardware](https://github.com/sinara-hw/Stabilizer/wiki/Stabilizer_v1.0_top_small.jpg)](https://github.com/sinara-hw/Stabilizer)
## Applications
This firmware offers a library of hardware and software functionality targeting the use of the Stabilizer hardware in various digital signal processing applications commonly occurring in Quantum Technology.
It provides abstractions over the fast analog inputs and outputs, time stamping, Pounder DDS interfaces and a collection of tailored and optimized digital signal processing algorithms (IIR, FIR, Lockin, PLL, reciprocal PLL, Unwrapper, Lowpass, Cosine-Sine, Atan2).
An application, which is the compiled firmware running on the device, can compose and configure these hardware and software components to implement different use cases.
Several applications are provided by default.
The following documentation links contain the application-specific settings and telemetry
information.
| Application | Documentation | Application Description |
| :---: | :--: | :---- |
| `dual-iir` | [Link]({{site.baseurl}}/firmware/dual_iir/index.html) | Two channel biquad IIR filter
| `lockin` | [Link]({{site.baseurl}}/firmware/lockin/index.html) | Lockin amplifier support various various reference sources |
### Library Documentation
The Stabilizer library docs contain documentation for common components used in all Stabilizer
applications.
The Stabilizer library documentation is available [here]({{site.baseurl}}/firmware/stabilizer/index.html).

View File

@ -0,0 +1,162 @@
---
title: Getting Started
layout: default
permalink: /getting-started
nav_order: 2
---
## Table of Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
---
# Getting Started
Getting started requires a few steps:
There are a number of steps that must be completed when first getting started with Stabilizer.
1. Configure the firmware
* This requires updating any parameters, such as static IP addresses and
sampling rate.
1. Build the application
* This requires compiling the code after configuration parameters have been
updated.
1. Upload the application
* Once fimrware has been built, it needs to be programmed onto the device.
1. Set up MQTT
* Stabilizer utilizes MQTT for telemetry and configuration.
The following sections will walk you through completing each of these steps.
# Program Stabilizer
Firmware is compiled and loaded onto Stabilizer to program a specific application.
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:
* Sample frequency
* Sample batch size
* MQTT Broker IP address
Parameters are configured by editing `src/configuration.rs`.
Refer to the [documentation]({{site.baseurl}}/firmware/stabilizer/configuration.rs) 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)
```bash
git clone https://github.com/quartiq/stabilizer
```
1. Get [rustup](https://rustup.rs/)
* The minimum supported Rust version (MSRV) is 1.52.0
1. Install target support
```bash
rustup target add thumbv7em-none-eabihf
```
1. Build Firmware
```bash
cargo build --release
```
## Uploading Firmware
Firmware is loaded onto stabilizer utilizing an ST-Link (V2-1 or greater) JTAG programmer.
* If a programmer is not available, please see the [alternative method](#alternative-using-usb)
Ensure the ST-Link is connected to Stabilizer as shown below.
![JTAG Connection]({{site.baseurl}}/assets/stabilizer-jtag.jpg)
All of the instructions below assume you have properly [`built the firmware`](#building-firmware).
Substitute `dual-iir` below with the application name you are flashing.
1. Install [cargo-binutils](https://github.com/rust-embedded/cargo-binutils/)
```bash
cargo install cargo-binutils
rustup component add llvm-tools-preview
```
1. Generate the binary file
```bash
cargo objcopy --release --bin dual-iir -- -O binary dual-iir.bin`
```
1. Copy `dual-iir.bin` into the ST-Link drive on your computer.
### Alternative: Using USB
If an ST-Link V2-1 or above is not available, you can upload firmware using a micro USB cable
plugged in to the front of Stabilizer.
1. Install the DFU USB tool [`dfu-util`](http://dfu-util.sourceforge.net)
1. Connect to the Micro USB connector below the RJ45
1. Short JC2/BOOT
1. Perform the Device Firmware Upgrade (DFU)
```bash
dfu-util -a 0 -s 0x08000000:leave -D dual-iir.bin
```
### Alternative: Firmware Development / Debug
The below instructions are useful for debugging or developing firmware
For an interactive flash experience with live logging, utilize `probe-run` as follows.
1. Install `probe-run`
```bash
cargo install probe-run
```
1. Build and run firmware on the device
```bash
cargo run --release --bin dual-iir
```
1. When using debug (non `--release`) mode, decrease the sampling frequency significantly.
The added error checking code and missing optimizations may lead to the code
missing deadlines and panicing.
# Set up and test MQTT
## Set up the Broker
Stabilizer requires an MQTT broker that supports MQTTv5. [Mosquitto](https://mosquitto.org/) has
been used during development, but any MQTTv5 broker is supported.
> _Note_: Mosquitto version 1 only supports MQTTv3.1. If using Mosquitto, ensure version 2.0.0 or
> 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.
## Test the Connection
Once your broker is running, test that Stabilizer is properly connected to it.
To do this, we will check that Stabilizer is reporting telemetry on the following topic:
```
dt/sinara/dual-iir/00-11-22-33-44-55/telemetry
```
> **Note**: The telemetry topic will be different based on the programmed application and the MAC address
of the device.
Download [MQTT-Explorer](http://mqtt-explorer.com/) to observe which topics have been posted to the
Broker.
![MQTT Explorer Configuration]({{site.baseurl}}/assets/mqtt-explorer.png)
> **Note**: Use the same broker address that you defined in the firmware for MQTT explorer.
Telemetry messages should come in approximately every 10 seconds when Stabilizer has connected to
the broker. Once you observe incoming telemetry, Stabilizer has been properly configured and is
operational.

63
docs/pages/networking.md Normal file
View File

@ -0,0 +1,63 @@
---
title: Networking
layout: default
nav_order: 4
permalink: /networking/
has_children: true
has_toc: false
---
## Table of Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
## MiniConf Run-time Settings
Stabilizer supports run-time settings configuration using MQTT.
Settings can be stored in the MQTT broker so that they are automatically applied whenever
Stabilizer reboots and connects. This is referred to as "retained" settings. Broker implementations
may optionally store these retained settings as well such that they will be reapplied between
restarts of the MQTT broker.
Settings are specific to a device. Any settings configured for one Stabilizer will not be applied
to another. Disambiguation of devices is done by using Stabilizer's MAC address.
Settings are specific to an application. If two identical settings exist for two different
applications, each application maintains its own independent value.
Refer to the [documentation](run-time-settings) for information on how to configure run-time
settings.
## Telemetry
Stabilizer applications publish telemetry utilizes MQTT for managing run-time settings configurations as well as live telemetry
reporting.
Telemetry is defined as low rate, general health information. It is not intended for high throughput
or efficiency. Telemetry is generally used to determine that the device is functioning nominally.
Stabilizer applications publish telemetry over MQTT at a set rate. Telemetry data units are defined
by the application. All telemetry is reported using standard JSON format.
Telemetry is intended for low-bandwidth monitoring. It is not intended to transfer large amounts of
data and uses a minimal amount of bandwidth. Telemetry is published using "best effort" semantics -
individual messages may be dropped or Stabilizer may fail to publish telemetry due to internal
buffering requirements.
In its most basic form, telemetry publishes the latest ADC input voltages, DAC output voltages, and
digital input states.
Refer to the respective [application documentation](/#applications) for more information on telemetry.
## Livestream
Stabilizer supports livestream capabilities for streaming real-time data over UDP. The livestream is
intended to be a high-bandwidth mechanism to transfer large amounts of data from Stabilizer to a
host computer for further analysis.
Livestreamed data is sent with "best effort" - it's possible that data may be lost either due to
network congestion or by Stabilizer.
Refer to the the respective [application documentation](/#applications) for more information.

View File

@ -0,0 +1,48 @@
---
title: Run-Time Settings
layout: default
permalink: /networking/run-time-settings
parent: Networking
---
# Settings Configuration
Stabilizer allows for run-time settings configurations using the `miniconf.py` utility script. This
script is in the root of the Stabilizer github repository.
## Setup
In order to use `miniconf.py`, run the following command:
```
python -m pip install gmqtt
```
## Usage
The `miniconf.py` script utilizes a unique "device prefix". The device prefix is always of the
form `dt/sinara/<app>/<mac-address>`, where `<app>` is the name of the application and
`<mac-address>` is the MAC address of the device, formatted with delimiting dashes.
Settings have a `path` and a `value` being configured. The `value` parameter is JSON-encoded data
and the `path` value is a path-like string.
As an example, for configuring `dual-iir`'s `stream_target`, the following information would be
used:
* `path` = `stream_target`
* `value` = `{"ip": [192, 168, 0, 1], "port": 4000}`
```
python miniconf.py --broker localhost dt/sinara/dual-iir/00-11-22-33-44-55 stream_target='{"ip": [192, 168, 0, 1], "port": 4000}'
```
The prefix can be found for a specific device by looking at the topic on which telemetry that is
being published.
Refer to the [application documentation](/#applications) for the exact settings and values exposed
for each application.
The rules for constructing `path` values are documented in [`miniconf`'s
documentation](https://github.com/quartiq/miniconf#settings-paths)
Refer to the documentation for [Miniconf]({{site.baseurl}}/firmware/miniconf/enum.Error.html) for a
description of the possible error codes that `miniconf.py` may return if the settings update was
unsuccessful.

View File

@ -12,7 +12,7 @@ miniconf = "0.1"
[dev-dependencies] [dev-dependencies]
easybench = "1.0" easybench = "1.0"
rand = "0.8" rand = "0.8"
ndarray = "0.15" ndarray = { default-features = false, version = "0.15" }
[[bench]] [[bench]]
name = "micro" name = "micro"

View File

@ -4,7 +4,7 @@ include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
/// Compute the cosine and sine of an angle. /// Compute the cosine and sine of an angle.
/// This is ported from the MiSoC cossin core. /// This is ported from the MiSoC cossin core.
/// (https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py) /// <https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py>
/// ///
/// # Arguments /// # Arguments
/// * `phase` - 32-bit phase. /// * `phase` - 32-bit phase.

View File

@ -39,6 +39,25 @@ pub type Vec5 = [f32; 5];
/// Therefore it can trivially implement bump-less transfer. /// Therefore it can trivially implement bump-less transfer.
/// * Cascading multiple IIR filters allows stable and robust /// * Cascading multiple IIR filters allows stable and robust
/// implementation of transfer functions beyond bequadratic terms. /// implementation of transfer functions beyond bequadratic terms.
///
/// # Miniconf
///
/// `{"y_offset": y0, "y_min": ym, "y_max": yM, "ba": [b0, b1, b2, -a1, -a2]}`
///
/// * `y0` is the output offset code
/// * `ym` is the lower saturation limit
/// * `yM` is the upper saturation limit
///
/// IIR filter tap gains (`ba`) are an array `[b0, b1, b2, a1, a2]` such that the
/// new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`.
/// The IIR coefficients can be mapped to other transfer function
/// representations, for example as described in <https://arxiv.org/abs/1508.06319>
///
///
/// ## Notes
/// The units of the IIR utilize 16-bit signed integers for full-scale. saturation and offset
/// parameter are given in this scale, where full-scale represents an output amplitude of 10.24
/// V.
#[derive(Copy, Clone, Debug, Default, Deserialize, MiniconfAtomic)] #[derive(Copy, Clone, Debug, Default, Deserialize, MiniconfAtomic)]
pub struct IIR { pub struct IIR {
pub ba: Vec5, pub ba: Vec5,

View File

@ -1,3 +1,30 @@
//! # Dual IIR
//!
//! The Dual IIR application exposes two configurable channels. Stabilizer samples input at a fixed
//! rate, digitally filters the data, and then generates filtered output signals on the respective
//! channel outputs.
//!
//! ## Features
//! * Two indpenendent channels
//! * up to 800 kHz rate, timed sampling
//! * Run-time filter configuration
//! * Input/Output data streaming
//! * Down to 2 µs latency
//! * f32 IIR math
//! * Generic biquad (second order) IIR filter
//! * Anti-windup
//! * Derivative kick avoidance
//!
//! ## Settings
//! Refer to the [Settings] structure for documentation of run-time configurable settings for this
//! application.
//!
//! ## Telemetry
//! Refer to [Telemetry] for information about telemetry reported by this application.
//!
//! ## Livestreaming
//! This application streams raw ADC and DAC data over UDP. Refer to
//! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information.
#![deny(warnings)] #![deny(warnings)]
#![no_std] #![no_std]
#![no_main] #![no_main]
@ -34,11 +61,63 @@ const IIR_CASCADE_LENGTH: usize = 1;
#[derive(Clone, Copy, Debug, Deserialize, Miniconf)] #[derive(Clone, Copy, Debug, Deserialize, Miniconf)]
pub struct Settings { pub struct Settings {
/// Configure the Analog Front End (AFE) gain.
///
/// # Path
/// `afe/<n>`
///
/// * <n> specifies which channel to configure. <n> := [0, 1]
///
/// # Value
/// Any of the variants of [Gain] enclosed in double quotes.
afe: [Gain; 2], afe: [Gain; 2],
/// Configure the IIR filter parameters.
///
/// # Path
/// `iir_ch/<n>/<m>`
///
/// * <n> specifies which channel to configure. <n> := [0, 1]
/// * <m> specifies which cascade to configure. <m> := [0, 1], depending on [IIR_CASCADE_LENGTH]
///
/// # Value
/// See [iir::IIR#miniconf]
iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2],
/// Specified true if DI1 should be used as a "hold" input.
///
/// # Path
/// `allow_hold`
///
/// # Value
/// "true" or "false"
allow_hold: bool, allow_hold: bool,
/// Specified true if "hold" should be forced regardless of DI1 state and hold allowance.
///
/// # Path
/// `force_hold`
///
/// # Value
/// "true" or "false"
force_hold: bool, force_hold: bool,
/// Specifies the telemetry output period in seconds.
///
/// # Path
/// `telemetry_period`
///
/// # Value
/// Any non-zero value less than 65536.
telemetry_period: u16, telemetry_period: u16,
/// Specifies the target for data livestreaming.
///
/// # Path
/// `stream_target`
///
/// # Value
/// See [StreamTarget#miniconf]
stream_target: StreamTarget, stream_target: StreamTarget,
} }

View File

@ -1,3 +1,29 @@
//! # Lockin
//!
//! THe `lockin` application implements a lock-in amplifier using either an external or internally
//! generated
//!
//! ## Features
//! * Up to 800 kHz sampling
//! * Up to 400 kHz modulation frequency
//! * Supports internal and external reference sources:
//! 1. Internal: Generate reference internally and output on one of the channel outputs
//! 2. External: Reciprocal PLL, reference input applied to DI0.
//! * Adjustable PLL and locking time constants
//! * Adjustable phase offset and harmonic index
//! * Run-time configurable output modes (in-phase, quadrature, magnitude, log2 power, phase, frequency)
//! * Input/output data streamng via UDP
//!
//! ## Settings
//! Refer to the [Settings] structure for documentation of run-time configurable settings for this
//! application.
//!
//! ## Telemetry
//! Refer to [Telemetry] for information about telemetry reported by this application.
//!
//! ## Livestreaming
//! This application streams raw ADC and DAC data over UDP. Refer to
//! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information.
#![deny(warnings)] #![deny(warnings)]
#![no_std] #![no_std]
#![no_main] #![no_main]
@ -8,6 +34,7 @@ use mutex_trait::prelude::*;
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
use stabilizer::{ use stabilizer::{
configuration,
hardware::{ hardware::{
self, self,
adc::{Adc0Input, Adc1Input, AdcCode}, adc::{Adc0Input, Adc1Input, AdcCode},
@ -38,35 +65,118 @@ const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] =
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)] #[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
enum Conf { enum Conf {
/// Output the lockin magnitude.
Magnitude, Magnitude,
/// Output the phase of the lockin
Phase, Phase,
/// Output the lockin reference frequency as a sinusoid
ReferenceFrequency, ReferenceFrequency,
/// Output the logarithmic power of the lockin
LogPower, LogPower,
/// Output the in-phase component of the lockin signal.
InPhase, InPhase,
/// Output the quadrature component of the lockin signal.
Quadrature, Quadrature,
/// Output the lockin internal modulation frequency as a sinusoid
Modulation, Modulation,
} }
#[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)] #[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)]
enum LockinMode { enum LockinMode {
/// Utilize an internally generated reference for demodulation
Internal, Internal,
/// Utilize an external modulation signal supplied to DI0
External, External,
} }
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)] #[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
pub struct Settings { pub struct Settings {
/// Configure the Analog Front End (AFE) gain.
///
/// # Path
/// `afe/<n>`
///
/// * <n> specifies which channel to configure. <n> := [0, 1]
///
/// # Value
/// Any of the variants of [Gain] enclosed in double quotes.
afe: [Gain; 2], afe: [Gain; 2],
/// Specifies the operational mode of the lockin.
///
/// # Path
/// `lockin_mode`
///
/// # Value
/// One of the variants of [LockinMode] enclosed in double quotes.
lockin_mode: LockinMode, lockin_mode: LockinMode,
/// Specifis the PLL time constant.
///
/// # Path
/// `pll_tc/<n>`
///
/// * <n> specifies which channel to configure. <n> := [0, 1]
///
/// # Value
/// The PLL time constant as an unsigned byte (0-255).
pll_tc: [u8; 2], pll_tc: [u8; 2],
/// Specifies the lockin time constant.
///
/// # Path
/// `lockin_tc`
///
/// # Value
/// The lockin low-pass time constant as an unsigned byte (0-255).
lockin_tc: u8, lockin_tc: u8,
/// Specifies which harmonic to use for the lockin.
///
/// # Path
/// `lockin_harmonic`
///
/// # Value
/// Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate)
lockin_harmonic: i32, lockin_harmonic: i32,
/// Specifies the LO phase offset.
///
/// # Path
/// `lockin_phase`
///
/// # Value
/// Demodulation LO phase offset. Units are in terms of i32, where [i32::MIN] is equivalent to
/// -pi and [i32::MAX] is equivalent to +pi.
lockin_phase: i32, lockin_phase: i32,
/// Specifies DAC output mode.
///
/// # Path
/// `output_conf/<n>`
///
/// * <n> specifies which channel to configure. <n> := [0, 1]
///
/// # Value
/// One of the variants of [Conf] enclosed in double quotes.
output_conf: [Conf; 2], output_conf: [Conf; 2],
/// Specifies the telemetry output period in seconds.
///
/// # Path
/// `telemetry_period`
///
/// # Value
/// Any non-zero value less than 65536.
telemetry_period: u16, telemetry_period: u16,
/// Specifies the target for data livestreaming.
///
/// # Path
/// `stream_target`
///
/// # Value
/// See [StreamTarget#miniconf]
stream_target: StreamTarget, stream_target: StreamTarget,
} }
@ -128,8 +238,8 @@ const APP: () = {
let settings = Settings::default(); let settings = Settings::default();
let pll = RPLL::new( let pll = RPLL::new(
design_parameters::ADC_SAMPLE_TICKS_LOG2 configuration::ADC_SAMPLE_TICKS_LOG2
+ design_parameters::SAMPLE_BUFFER_SIZE_LOG2, + configuration::SAMPLE_BUFFER_SIZE_LOG2,
); );
// Spawn a settings and telemetry update for default settings. // Spawn a settings and telemetry update for default settings.
@ -204,8 +314,7 @@ const APP: () = {
); );
( (
pll_phase, pll_phase,
(pll_frequency (pll_frequency >> configuration::SAMPLE_BUFFER_SIZE_LOG2)
>> design_parameters::SAMPLE_BUFFER_SIZE_LOG2)
as i32, as i32,
) )
} }
@ -213,7 +322,7 @@ const APP: () = {
// Reference phase and frequency are known. // Reference phase and frequency are known.
( (
1i32 << 30, 1i32 << 30,
1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2), 1i32 << (32 - configuration::SAMPLE_BUFFER_SIZE_LOG2),
) )
} }
}; };

41
src/configuration.rs Normal file
View File

@ -0,0 +1,41 @@
//! 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 100MHz.
///
/// ## Example
/// With a value of 7, this corresponds to 2^7 = 128 ticks. Each tick of the 100MHz timer requires
/// 10ns.
///
/// Sampling Period = 10ns * 128 = 1.28 us
/// Sampling Frequency = 781.25KHz
///
/// Or more succinctly:
/// `F_s = 100MHz / (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

@ -42,15 +42,11 @@ 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 // The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
// equal to 10ns per tick. // equal to 10ns per tick.
// Currently, the sample rate is equal to: Fsample = 100/128 MHz ~ 800 KHz pub const ADC_SAMPLE_TICKS: u16 =
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7; 1 << crate::configuration::ADC_SAMPLE_TICKS_LOG2;
pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2;
// The desired ADC sample processing buffer size. // The desired ADC sample processing buffer size.
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; pub const SAMPLE_BUFFER_SIZE: usize =
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; 1 << crate::configuration::SAMPLE_BUFFER_SIZE_LOG2;
pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE]; pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE];
// The MQTT broker IPv4 address
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];

View File

@ -16,7 +16,7 @@
///! capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the ///! 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 ///! 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. ///! the last sample of the batch. That sample is then available for processing by the user.
use crate::hardware::{design_parameters, timers}; use crate::{configuration, hardware::timers};
use core::convert::TryFrom; use core::convert::TryFrom;
use stm32h7xx_hal as hal; use stm32h7xx_hal as hal;
@ -58,9 +58,7 @@ impl Timestamper {
// Capture at the batch period. // Capture at the batch period.
input_capture.configure_prescaler( input_capture.configure_prescaler(
timers::Prescaler::try_from( timers::Prescaler::try_from(configuration::SAMPLE_BUFFER_SIZE_LOG2)
design_parameters::SAMPLE_BUFFER_SIZE_LOG2,
)
.unwrap(), .unwrap(),
); );

View File

@ -45,10 +45,11 @@ impl SystemTimer {
} }
impl rtic::Monotonic for SystemTimer { impl rtic::Monotonic for SystemTimer {
// Instants are stored in 32-bit signed integers. With a 10KHz tick rate, this means an /// Instants are stored in 32-bit signed integers. With a 10KHz tick rate, this means an
// instant can store up to ~59 hours of time before overflowing. /// instant can store up to ~59 hours of time before overflowing.
type Instant = i32; type Instant = i32;
/// The ratio of the CPU clock to the system timer.
fn ratio() -> rtic::Fraction { fn ratio() -> rtic::Fraction {
rtic::Fraction { rtic::Fraction {
// At 10KHz with a 400MHz CPU clock, the CPU clock runs 40,000 times faster than // At 10KHz with a 400MHz CPU clock, the CPU clock runs 40,000 times faster than

View File

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

View File

@ -1,20 +1,15 @@
///! Stabilizer data stream capabilities //! Stabilizer data stream capabilities
///! //!
///! # Design //! # Design
///! Stabilizer data streamining utilizes UDP packets to send live data streams at high throughput. //! Data streamining utilizes UDP packets to send live data streams at high throughput.
///! Packets are always sent in a best-effort fashion, and data may be dropped. Each packet contains //! Packets are always sent in a best-effort fashion, and data may be dropped. Each packet contains
///! an identifier that can be used to detect any dropped data. //! an identifier that can be used to detect dropped data.
///! //!
///! The current implementation utilizes an single-producer, single-consumer queue to send data //! Refer to [DataPacket] for information about the serialization format of each UDP packet.
///! between a high priority task and the UDP transmitter. //!
///! //! # Example
///! A "batch" of data is defined to be a single item in the SPSC queue sent to the UDP transmitter //! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception
///! thread. The transmitter thread then serializes as many sequential "batches" into a single UDP //! of livestreamed data.
///! packet as possible. The UDP packet is also given a header indicating the starting batch
///! sequence number and the number of batches present. If the UDP transmitter encounters a
///! non-sequential batch, it does not enqueue it into the packet and instead transmits any staged
///! data. The non-sequential batch is then transmitted in a new UDP packet. This method allows a
///! receiver to detect dropped batches (e.g. due to processing overhead).
use heapless::spsc::{Consumer, Producer, Queue}; use heapless::spsc::{Consumer, Producer, Queue};
use miniconf::MiniconfAtomic; use miniconf::MiniconfAtomic;
use serde::Deserialize; use serde::Deserialize;
@ -30,6 +25,15 @@ const BLOCK_BUFFER_SIZE: usize = 30;
const SUBSAMPLE_RATE: usize = 1; const SUBSAMPLE_RATE: usize = 1;
/// Represents the destination for the UDP stream to send data to. /// Represents the destination for the UDP stream to send data to.
///
/// # Miniconf
/// `{"ip": <addr>, "port": <port>}`
///
/// * `<addr>` is an array of 4 bytes. E.g. `[192, 168, 0, 1]`
/// * `<port>` is any unsigned 16-bit value.
///
/// ## Example
/// `{"ip": [192, 168,0, 1], "port": 1111}`
#[derive(Copy, Clone, Debug, MiniconfAtomic, Deserialize, Default)] #[derive(Copy, Clone, Debug, MiniconfAtomic, Deserialize, Default)]
pub struct StreamTarget { pub struct StreamTarget {
pub ip: [u8; 4], pub ip: [u8; 4],
@ -125,22 +129,74 @@ impl BlockGenerator {
} }
} }
/// # Stream Packet
/// Represents a single UDP packet sent by the stream. /// Represents a single UDP packet sent by the stream.
/// ///
/// # Packet Format /// A "batch" of data is defined to be the data collected for a single invocation of the DSP
/// All data is sent in network-endian format. The format is as follows /// routine. A packet is composed of as many sequential batches as can fit.
/// ///
/// Header: /// The packet is given a header indicating the starting batch sequence number and the number of
/// [0..2]: Start block ID (u16) /// batches present. If the UDP transmitter encounters a non-sequential batch, it does not enqueue
/// [2..3]: Num Blocks present (u8) <N> /// it into the packet and instead transmits any staged data. The non-sequential batch is then
/// [3..4]: Batch Size (u8) <BS> /// transmitted in a new UDP packet. This method allows a receiver to detect dropped batches (e.g.
/// due to processing overhead).
/// ///
/// Following the header, batches are added sequentially. Each batch takes the form of: /// ## Data Format
/// [<BS>*0..<BS>*2]: ADC0 ///
/// [<BS>*2..<BS>*4]: ADC1 /// Data sent via UDP is sent in "blocks". Each block is a single batch of ADC/DAC codes from an
/// [<BS>*4..<BS>*6]: DAC0 /// individual DSP processing routine. Each block is assigned a unique 16-bit identifier. The identifier
/// [<BS>*6..<BS>*8]: DAC1 /// increments by one for each block and rolls over. All blocks in a single packet are guaranteed to
struct DataPacket<'a> { /// contain sequential identifiers.
///
/// All data is transmitted in network-endian (big-endian) format.
///
/// ### Quick Reference
///
/// In the reference below, any values enclosed in parentheses represents the number of bytes used for
/// that value. E.g. "Batch size (1)" indicates 1 byte is used to represent the batch size.
/// ```
/// # UDP packets take the following form
/// <Header>,<Batch 1>,[<Batch 2>, ...<Batch N>]
///
/// # The header takes the following form
/// <Header> = <Starting ID (2)>,<Number blocks [N] (1)>,<Batch size [BS] (1)>
///
/// # Each batch takes the following form
/// <Batch N> = <ADC0>,<ADC1>,<DAC0>,<DAC1>
///
/// # Where
/// <ADCx/DACx> = <Sample 1 (2)>, ...<Sample BS (2)>
/// ```
///
/// ### Packet Format
/// Multiple blocks are sent in a single UDP packet simultaneously. Each UDP packet transmitted
/// contains a header followed by the serialized data blocks.
/// ```
/// <Header>,<Batch 1>,[<Batch 2>, ...<Batch N>]
/// ```
///
/// ### Header
/// A header takes the following form:
/// * The starting block ID (2 bytes)
/// * The number of blocks present in the packet (1 byte)
/// * The size of each bach in samples (1 byte)
///
/// ```
/// <Starting ID (2)>,<N blocks (1)>,<Batch size (1)>
/// ```
///
/// ### Data Blocks
/// Following the header, each block is sequentially serialized. Each block takes the following form:
/// ```
/// <ADC0 samples>,<ADC1 samples>,<DAC0 samples>,<DAC1 samples>
/// ```
///
/// Where `<XXX samples>` is an array of N 16-bit ADC/DAC samples. The number of samples is provided in the
/// header.
///
/// ADC and DAC codes are transmitted in raw machine-code format. Please refer to the datasheet for the
/// ADC and DAC if you need to convert these to voltages.
pub struct DataPacket<'a> {
buf: &'a mut [u8], buf: &'a mut [u8],
subsample_rate: usize, subsample_rate: usize,
start_id: Option<u16>, start_id: Option<u16>,

View File

@ -14,7 +14,7 @@ use heapless::String;
use log::info; use log::info;
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
use crate::hardware::design_parameters::MQTT_BROKER; use crate::configuration::MQTT_BROKER;
/// MQTT settings interface. /// MQTT settings interface.
pub struct MiniconfClient<S> pub struct MiniconfClient<S>

View File

@ -15,9 +15,8 @@ use minimq::QoS;
use serde::Serialize; use serde::Serialize;
use super::NetworkReference; use super::NetworkReference;
use crate::hardware::{ use crate::configuration::MQTT_BROKER;
adc::AdcCode, afe::Gain, dac::DacCode, design_parameters::MQTT_BROKER, use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode};
};
/// The telemetry client for reporting telemetry data over MQTT. /// The telemetry client for reporting telemetry data over MQTT.
pub struct TelemetryClient<T: Serialize> { pub struct TelemetryClient<T: Serialize> {
@ -49,8 +48,13 @@ pub struct TelemetryBuffer {
/// overhead. /// overhead.
#[derive(Serialize)] #[derive(Serialize)]
pub struct Telemetry { pub struct Telemetry {
/// Most recent input voltage measurement.
adcs: [f32; 2], adcs: [f32; 2],
/// Most recent output voltage.
dacs: [f32; 2], dacs: [f32; 2],
/// Most recent digital input assertion state.
digital_inputs: [bool; 2], digital_inputs: [bool; 2],
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 130 B