diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf68b3f..848060e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,3 +83,29 @@ jobs: with: command: bench 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 diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml new file mode 100644 index 0000000..b3279d5 --- /dev/null +++ b/.github/workflows/release-docs.yml @@ -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 diff --git a/.gitignore b/.gitignore index ea8c4bf..a6eef6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +docs/_site/ diff --git a/Cargo.lock b/Cargo.lock index db78e72..03273f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ [[package]] name = "derive_miniconf" 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 = [ "proc-macro2", "quote", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "miniconf" 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 = [ "derive_miniconf", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6f52210..1c6c0d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ rev = "a2e3ad5" [patch.crates-io.miniconf] git = "https://github.com/quartiq/miniconf.git" -rev = "2750533" +rev = "9c826f8" [dependencies.smoltcp-nal] git = "https://github.com/quartiq/smoltcp-nal.git" diff --git a/README.md b/README.md index d34d971..ba1759d 100644 --- a/README.md +++ b/README.md @@ -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. 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. -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. diff --git a/doc/.keep-me b/doc/.keep-me deleted file mode 100644 index e69de29..0000000 diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 0000000..7b3f249 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem "just-the-docs" +gem "jekyll-remote-theme" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 0000000..3c8dd31 --- /dev/null +++ b/docs/Gemfile.lock @@ -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 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f0df354 --- /dev/null +++ b/docs/README.md @@ -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 +``` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..a7901db --- /dev/null +++ b/docs/_config.yml @@ -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 diff --git a/docs/assets/mqtt-explorer.png b/docs/assets/mqtt-explorer.png new file mode 100644 index 0000000..9ffe74c Binary files /dev/null and b/docs/assets/mqtt-explorer.png differ diff --git a/docs/assets/stabilizer-jtag.jpg b/docs/assets/stabilizer-jtag.jpg new file mode 100644 index 0000000..6535fda Binary files /dev/null and b/docs/assets/stabilizer-jtag.jpg differ diff --git a/docs/assets/stabilizer-logo.png b/docs/assets/stabilizer-logo.png new file mode 100644 index 0000000..a5cfb76 Binary files /dev/null and b/docs/assets/stabilizer-logo.png differ diff --git a/stabilizer_pid.svg b/docs/assets/stabilizer_pid.svg similarity index 100% rename from stabilizer_pid.svg rename to docs/assets/stabilizer_pid.svg diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..a6cbb9e Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..16e62b0 --- /dev/null +++ b/docs/index.md @@ -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). diff --git a/docs/pages/getting-started.md b/docs/pages/getting-started.md new file mode 100644 index 0000000..6db58f6 --- /dev/null +++ b/docs/pages/getting-started.md @@ -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. diff --git a/docs/pages/networking.md b/docs/pages/networking.md new file mode 100644 index 0000000..226bc32 --- /dev/null +++ b/docs/pages/networking.md @@ -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. diff --git a/docs/pages/networking/run-time-settings.md b/docs/pages/networking/run-time-settings.md new file mode 100644 index 0000000..c96c3c3 --- /dev/null +++ b/docs/pages/networking/run-time-settings.md @@ -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//`, where `` is the name of the application and +`` 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. diff --git a/dsp/Cargo.toml b/dsp/Cargo.toml index e3be45b..82e4a84 100644 --- a/dsp/Cargo.toml +++ b/dsp/Cargo.toml @@ -12,7 +12,7 @@ miniconf = "0.1" [dev-dependencies] easybench = "1.0" rand = "0.8" -ndarray = "0.15" +ndarray = { default-features = false, version = "0.15" } [[bench]] name = "micro" diff --git a/dsp/src/cossin.rs b/dsp/src/cossin.rs index 5dae7c3..abca821 100644 --- a/dsp/src/cossin.rs +++ b/dsp/src/cossin.rs @@ -4,7 +4,7 @@ include!(concat!(env!("OUT_DIR"), "/cossin_table.rs")); /// Compute the cosine and sine of an angle. /// This is ported from the MiSoC cossin core. -/// (https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py) +/// /// /// # Arguments /// * `phase` - 32-bit phase. diff --git a/dsp/src/iir.rs b/dsp/src/iir.rs index a6af191..de5bb68 100644 --- a/dsp/src/iir.rs +++ b/dsp/src/iir.rs @@ -39,6 +39,25 @@ pub type Vec5 = [f32; 5]; /// Therefore it can trivially implement bump-less transfer. /// * Cascading multiple IIR filters allows stable and robust /// 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 +/// +/// +/// ## 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)] pub struct IIR { pub ba: Vec5, diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index bad6f97..d0aca33 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -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)] #![no_std] #![no_main] @@ -34,11 +61,63 @@ const IIR_CASCADE_LENGTH: usize = 1; #[derive(Clone, Copy, Debug, Deserialize, Miniconf)] pub struct Settings { + /// Configure the Analog Front End (AFE) gain. + /// + /// # Path + /// `afe/` + /// + /// * specifies which channel to configure. := [0, 1] + /// + /// # Value + /// Any of the variants of [Gain] enclosed in double quotes. afe: [Gain; 2], + + /// Configure the IIR filter parameters. + /// + /// # Path + /// `iir_ch//` + /// + /// * specifies which channel to configure. := [0, 1] + /// * specifies which cascade to configure. := [0, 1], depending on [IIR_CASCADE_LENGTH] + /// + /// # Value + /// See [iir::IIR#miniconf] 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, + + /// Specified true if "hold" should be forced regardless of DI1 state and hold allowance. + /// + /// # Path + /// `force_hold` + /// + /// # Value + /// "true" or "false" force_hold: bool, + + /// Specifies the telemetry output period in seconds. + /// + /// # Path + /// `telemetry_period` + /// + /// # Value + /// Any non-zero value less than 65536. telemetry_period: u16, + + /// Specifies the target for data livestreaming. + /// + /// # Path + /// `stream_target` + /// + /// # Value + /// See [StreamTarget#miniconf] stream_target: StreamTarget, } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 4175771..33332d4 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -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)] #![no_std] #![no_main] @@ -8,6 +34,7 @@ use mutex_trait::prelude::*; use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; use stabilizer::{ + configuration, hardware::{ self, adc::{Adc0Input, Adc1Input, AdcCode}, @@ -38,35 +65,118 @@ const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] = #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { + /// Output the lockin magnitude. Magnitude, + /// Output the phase of the lockin Phase, + /// Output the lockin reference frequency as a sinusoid ReferenceFrequency, + /// Output the logarithmic power of the lockin LogPower, + /// Output the in-phase component of the lockin signal. InPhase, + /// Output the quadrature component of the lockin signal. Quadrature, + /// Output the lockin internal modulation frequency as a sinusoid Modulation, } #[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)] enum LockinMode { + /// Utilize an internally generated reference for demodulation Internal, + /// Utilize an external modulation signal supplied to DI0 External, } #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] pub struct Settings { + /// Configure the Analog Front End (AFE) gain. + /// + /// # Path + /// `afe/` + /// + /// * specifies which channel to configure. := [0, 1] + /// + /// # Value + /// Any of the variants of [Gain] enclosed in double quotes. 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, + /// Specifis the PLL time constant. + /// + /// # Path + /// `pll_tc/` + /// + /// * specifies which channel to configure. := [0, 1] + /// + /// # Value + /// The PLL time constant as an unsigned byte (0-255). 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, + + /// 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, + + /// 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, + /// Specifies DAC output mode. + /// + /// # Path + /// `output_conf/` + /// + /// * specifies which channel to configure. := [0, 1] + /// + /// # Value + /// One of the variants of [Conf] enclosed in double quotes. 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, + /// Specifies the target for data livestreaming. + /// + /// # Path + /// `stream_target` + /// + /// # Value + /// See [StreamTarget#miniconf] stream_target: StreamTarget, } @@ -128,8 +238,8 @@ const APP: () = { let settings = Settings::default(); let pll = RPLL::new( - design_parameters::ADC_SAMPLE_TICKS_LOG2 - + design_parameters::SAMPLE_BUFFER_SIZE_LOG2, + configuration::ADC_SAMPLE_TICKS_LOG2 + + configuration::SAMPLE_BUFFER_SIZE_LOG2, ); // Spawn a settings and telemetry update for default settings. @@ -204,8 +314,7 @@ const APP: () = { ); ( pll_phase, - (pll_frequency - >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) + (pll_frequency >> configuration::SAMPLE_BUFFER_SIZE_LOG2) as i32, ) } @@ -213,7 +322,7 @@ const APP: () = { // Reference phase and frequency are known. ( 1i32 << 30, - 1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2), + 1i32 << (32 - configuration::SAMPLE_BUFFER_SIZE_LOG2), ) } }; diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..8b43eea --- /dev/null +++ b/src/configuration.rs @@ -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; diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index d04cc75..4ac28e3 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -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 // equal to 10ns per tick. -// Currently, the sample rate is equal to: Fsample = 100/128 MHz ~ 800 KHz -pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7; -pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; +pub const ADC_SAMPLE_TICKS: u16 = + 1 << crate::configuration::ADC_SAMPLE_TICKS_LOG2; // The desired ADC sample processing buffer size. -pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; -pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; +pub const SAMPLE_BUFFER_SIZE: usize = + 1 << crate::configuration::SAMPLE_BUFFER_SIZE_LOG2; pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE]; - -// The MQTT broker IPv4 address -pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10]; diff --git a/src/hardware/pounder/timestamp.rs b/src/hardware/pounder/timestamp.rs index 7f8241d..262af9b 100644 --- a/src/hardware/pounder/timestamp.rs +++ b/src/hardware/pounder/timestamp.rs @@ -16,7 +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::hardware::{design_parameters, timers}; +use crate::{configuration, hardware::timers}; use core::convert::TryFrom; use stm32h7xx_hal as hal; @@ -58,10 +58,8 @@ impl Timestamper { // Capture at the batch period. input_capture.configure_prescaler( - timers::Prescaler::try_from( - design_parameters::SAMPLE_BUFFER_SIZE_LOG2, - ) - .unwrap(), + timers::Prescaler::try_from(configuration::SAMPLE_BUFFER_SIZE_LOG2) + .unwrap(), ); Self { diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 7b445c4..a26d9e9 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -45,10 +45,11 @@ impl SystemTimer { } impl rtic::Monotonic for SystemTimer { - // 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. + /// 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. type Instant = i32; + /// The ratio of the CPU clock to the system timer. fn ratio() -> rtic::Fraction { rtic::Fraction { // At 10KHz with a 400MHz CPU clock, the CPU clock runs 40,000 times faster than diff --git a/src/lib.rs b/src/lib.rs index 85964a7..58c85af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] +pub mod configuration; pub mod hardware; pub mod net; diff --git a/src/net/data_stream.rs b/src/net/data_stream.rs index 4b926ed..e5b69ce 100644 --- a/src/net/data_stream.rs +++ b/src/net/data_stream.rs @@ -1,20 +1,15 @@ -///! Stabilizer data stream capabilities -///! -///! # Design -///! Stabilizer 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 -///! an identifier that can be used to detect any dropped data. -///! -///! The current implementation utilizes an single-producer, single-consumer queue to send data -///! between a high priority task and the UDP transmitter. -///! -///! A "batch" of data is defined to be a single item in the SPSC queue sent to the UDP transmitter -///! thread. The transmitter thread then serializes as many sequential "batches" into a single UDP -///! 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). +//! Stabilizer data stream capabilities +//! +//! # Design +//! 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 +//! an identifier that can be used to detect dropped data. +//! +//! Refer to [DataPacket] for information about the serialization format of each UDP packet. +//! +//! # Example +//! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception +//! of livestreamed data. use heapless::spsc::{Consumer, Producer, Queue}; use miniconf::MiniconfAtomic; use serde::Deserialize; @@ -30,6 +25,15 @@ const BLOCK_BUFFER_SIZE: usize = 30; const SUBSAMPLE_RATE: usize = 1; /// Represents the destination for the UDP stream to send data to. +/// +/// # Miniconf +/// `{"ip": , "port": }` +/// +/// * `` is an array of 4 bytes. E.g. `[192, 168, 0, 1]` +/// * `` is any unsigned 16-bit value. +/// +/// ## Example +/// `{"ip": [192, 168,0, 1], "port": 1111}` #[derive(Copy, Clone, Debug, MiniconfAtomic, Deserialize, Default)] pub struct StreamTarget { pub ip: [u8; 4], @@ -125,22 +129,74 @@ impl BlockGenerator { } } +/// # Stream Packet /// Represents a single UDP packet sent by the stream. /// -/// # Packet Format -/// All data is sent in network-endian format. The format is as follows +/// A "batch" of data is defined to be the data collected for a single invocation of the DSP +/// routine. A packet is composed of as many sequential batches as can fit. /// -/// Header: -/// [0..2]: Start block ID (u16) -/// [2..3]: Num Blocks present (u8) -/// [3..4]: Batch Size (u8) +/// The packet is 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). /// -/// Following the header, batches are added sequentially. Each batch takes the form of: -/// [*0..*2]: ADC0 -/// [*2..*4]: ADC1 -/// [*4..*6]: DAC0 -/// [*6..*8]: DAC1 -struct DataPacket<'a> { +/// ## Data Format +/// +/// Data sent via UDP is sent in "blocks". Each block is a single batch of ADC/DAC codes from an +/// individual DSP processing routine. Each block is assigned a unique 16-bit identifier. The identifier +/// increments by one for each block and rolls over. All blocks in a single packet are guaranteed to +/// 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 +///
,,[, ...] +/// +/// # The header takes the following form +///
= ,, +/// +/// # Each batch takes the following form +/// = ,,, +/// +/// # Where +/// = , ... +/// ``` +/// +/// ### 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 +/// 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) +/// +/// ``` +/// ,, +/// ``` +/// +/// ### Data Blocks +/// Following the header, each block is sequentially serialized. Each block takes the following form: +/// ``` +/// ,,, +/// ``` +/// +/// Where `` 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], subsample_rate: usize, start_id: Option, diff --git a/src/net/miniconf_client.rs b/src/net/miniconf_client.rs index 4bdd2b1..0caf8d0 100644 --- a/src/net/miniconf_client.rs +++ b/src/net/miniconf_client.rs @@ -14,7 +14,7 @@ use heapless::String; use log::info; use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; -use crate::hardware::design_parameters::MQTT_BROKER; +use crate::configuration::MQTT_BROKER; /// MQTT settings interface. pub struct MiniconfClient diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 40a5ce3..5ebfa3b 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -15,9 +15,8 @@ use minimq::QoS; use serde::Serialize; use super::NetworkReference; -use crate::hardware::{ - adc::AdcCode, afe::Gain, dac::DacCode, design_parameters::MQTT_BROKER, -}; +use crate::configuration::MQTT_BROKER; +use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode}; /// The telemetry client for reporting telemetry data over MQTT. pub struct TelemetryClient { @@ -49,8 +48,13 @@ pub struct TelemetryBuffer { /// overhead. #[derive(Serialize)] pub struct Telemetry { + /// Most recent input voltage measurement. adcs: [f32; 2], + + /// Most recent output voltage. dacs: [f32; 2], + + /// Most recent digital input assertion state. digital_inputs: [bool; 2], } diff --git a/stabilizer_pid.png b/stabilizer_pid.png index 981112f..186e09b 100644 Binary files a/stabilizer_pid.png and b/stabilizer_pid.png differ