Merge branch 'master' into feature/scan-mode

This commit is contained in:
Ryan Summers 2021-07-19 11:55:18 +02:00
commit 6d8273ec42
35 changed files with 973 additions and 166 deletions

1
.github/bors.toml vendored
View File

@ -5,5 +5,6 @@ status = [
"style", "style",
"test (stable)", "test (stable)",
"compile (stable)", "compile (stable)",
"doc",
"HITL Run Status" "HITL Run Status"
] ]

View File

@ -83,3 +83,57 @@ 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/setup-ruby@v1
with:
ruby-version: 2.7.x
- name: Setup Jekyll
working-directory: docs
run: |
gem update
gem update bundler
gem install jekyll bundler
bundler install
- 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
# We intentionally ignore fragments, as RTIC may generate fragments for various
# auto-generated code.
args: --dir target/thumbv7em-none-eabihf/doc --ignore-fragments --check-http --check-intra-doc-links
- name: Move firmware documents
run: |
mkdir -p docs/_site/stabilizer
- name: Test User Manual
working-directory: docs
run: |
rake build
mv ../target/thumbv7em-none-eabihf/doc _site/stabilizer/firmware
rake test

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

@ -0,0 +1,36 @@
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
force_orphan: true

1
.gitignore vendored
View File

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

52
Cargo.lock generated
View File

@ -38,7 +38,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9a69a963b70ddacfcd382524f72a4576f359af9334b3bf48a79566590bb8bfa" checksum = "b9a69a963b70ddacfcd382524f72a4576f359af9334b3bf48a79566590bb8bfa"
dependencies = [ dependencies = [
"bitrate", "bitrate",
"cortex-m 0.7.2", "cortex-m 0.7.3",
"embedded-hal", "embedded-hal",
] ]
@ -48,7 +48,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30302dda7a66f8c55932ebf208f7def840743ff64d495e9ceffcd97c18f11d39" checksum = "30302dda7a66f8c55932ebf208f7def840743ff64d495e9ceffcd97c18f11d39"
dependencies = [ dependencies = [
"cortex-m 0.7.2", "cortex-m 0.7.3",
] ]
[[package]] [[package]]
@ -126,15 +126,15 @@ dependencies = [
"aligned", "aligned",
"bare-metal 0.2.5", "bare-metal 0.2.5",
"bitfield", "bitfield",
"cortex-m 0.7.2", "cortex-m 0.7.3",
"volatile-register", "volatile-register",
] ]
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643a210c1bdc23d0db511e2a576082f4ff4dcae9d0c37f50b431b8f8439d6d6b" checksum = "2ac919ef424449ec8c08d515590ce15d9262c0ca5f0da5b0c901e971a3b783b3"
dependencies = [ dependencies = [
"bare-metal 0.2.5", "bare-metal 0.2.5",
"bitfield", "bitfield",
@ -162,9 +162,9 @@ dependencies = [
[[package]] [[package]]
name = "cortex-m-rtic" name = "cortex-m-rtic"
version = "0.5.6" version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa43f63284b363ac64f9ce5221a0f593b54f73258aba8e1a88c1feed8efdb664" checksum = "9845c4c7f7af19e216a2d00345f7f1507b8907b85cd551e403d68baeec342bb3"
dependencies = [ dependencies = [
"cortex-m 0.6.7", "cortex-m 0.6.7",
"cortex-m-rt", "cortex-m-rt",
@ -176,9 +176,9 @@ dependencies = [
[[package]] [[package]]
name = "cortex-m-rtic-macros" name = "cortex-m-rtic-macros"
version = "0.5.2" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1a6a4c9550373038c0e21a78d44d529bd697c25bbf6b8004bddc6e63b119c7" checksum = "cc874eda99515b15e67f03562726a530388f454431096d30131051b52b840559"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -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",
@ -250,7 +250,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db9efecb57ab54fa918730f2874d7d37647169c50fa1357fecb81abee840b113" checksum = "db9efecb57ab54fa918730f2874d7d37647169c50fa1357fecb81abee840b113"
dependencies = [ dependencies = [
"heapless 0.7.2", "heapless 0.7.3",
"nb 1.0.0", "nb 1.0.0",
"no-std-net", "no-std-net",
] ]
@ -352,9 +352,9 @@ dependencies = [
[[package]] [[package]]
name = "heapless" name = "heapless"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e44a9fba8877c568fc30457f5639d0aa7f57e494e4cdbc464150740a66c6613a" checksum = "34e26526e7168021f34243a3c8faac4dc4f938cde75a0f9b8e373cca5eb4e7ce"
dependencies = [ dependencies = [
"atomic-polyfill", "atomic-polyfill",
"hash32 0.2.1", "hash32 0.2.1",
@ -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",
@ -429,7 +429,7 @@ dependencies = [
"bit_field", "bit_field",
"embedded-nal", "embedded-nal",
"enum-iterator", "enum-iterator",
"heapless 0.7.2", "heapless 0.7.3",
] ]
[[package]] [[package]]
@ -509,9 +509,9 @@ dependencies = [
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.5.1" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" checksum = "e5adf0198d427ee515335639f275e806ca01acf9f07d7cf14bb36a10532a6169"
dependencies = [ dependencies = [
"derivative", "derivative",
"num_enum_derive", "num_enum_derive",
@ -519,9 +519,9 @@ dependencies = [
[[package]] [[package]]
name = "num_enum_derive" name = "num_enum_derive"
version = "0.5.1" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" checksum = "b1def5a3f69d4707d8a040b12785b98029a39e8c610ae685c7f6265669767482"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -645,7 +645,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065d6058bb1204f51a562a67209e1817cf714759d5cf845aa45c75fa7b0b9d9b" checksum = "065d6058bb1204f51a562a67209e1817cf714759d5cf845aa45c75fa7b0b9d9b"
dependencies = [ dependencies = [
"cortex-m 0.7.2", "cortex-m 0.7.3",
"ufmt-write", "ufmt-write",
] ]
@ -721,7 +721,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8014aeea272bca0f0779778d43253f2f3375b414185b30e6ecc4d3e4a9994781" checksum = "8014aeea272bca0f0779778d43253f2f3375b414185b30e6ecc4d3e4a9994781"
dependencies = [ dependencies = [
"heapless 0.7.2", "heapless 0.7.3",
"ryu", "ryu",
"serde", "serde",
] ]
@ -763,7 +763,7 @@ version = "0.1.0"
source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=5baf55f#5baf55fafbfe2c08d9fe56c836171e9d2fb468e8" source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=5baf55f#5baf55fafbfe2c08d9fe56c836171e9d2fb468e8"
dependencies = [ dependencies = [
"embedded-nal", "embedded-nal",
"heapless 0.7.2", "heapless 0.7.3",
"nanorand", "nanorand",
"smoltcp", "smoltcp",
] ]
@ -774,12 +774,12 @@ version = "0.5.0"
dependencies = [ dependencies = [
"ad9959", "ad9959",
"asm-delay", "asm-delay",
"cortex-m 0.7.2", "cortex-m 0.7.3",
"cortex-m-rt", "cortex-m-rt",
"cortex-m-rtic", "cortex-m-rtic",
"dsp", "dsp",
"embedded-hal", "embedded-hal",
"heapless 0.7.2", "heapless 0.7.3",
"log", "log",
"mcp23017", "mcp23017",
"miniconf", "miniconf",
@ -810,7 +810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b672c837e0ee8158ecc7fce0f9a948dd0693a9c588338e728d14b73307a0b7d" checksum = "8b672c837e0ee8158ecc7fce0f9a948dd0693a9c588338e728d14b73307a0b7d"
dependencies = [ dependencies = [
"bare-metal 0.2.5", "bare-metal 0.2.5",
"cortex-m 0.7.2", "cortex-m 0.7.3",
"cortex-m-rt", "cortex-m-rt",
"vcell", "vcell",
] ]
@ -822,7 +822,7 @@ source = "git+https://github.com/quartiq/stm32h7xx-hal.git?rev=33aa67d#33aa67d74
dependencies = [ dependencies = [
"bare-metal 1.0.0", "bare-metal 1.0.0",
"cast", "cast",
"cortex-m 0.7.2", "cortex-m 0.7.3",
"cortex-m-rt", "cortex-m-rt",
"embedded-dma", "embedded-dma",
"embedded-hal", "embedded-hal",

View File

@ -1,6 +1,7 @@
[package] [package]
name = "stabilizer" name = "stabilizer"
version = "0.5.0" version = "0.5.0"
resolver = "2"
authors = ["Robert Jördens <rj@quartiq.de>"] authors = ["Robert Jördens <rj@quartiq.de>"]
description = "Firmware for the Sinara Stabilizer board (stm32h743, eth, poe, 2 adc, 2 dac)" description = "Firmware for the Sinara Stabilizer board (stm32h743, eth, poe, 2 adc, 2 dac)"
categories = ["embedded", "no-std", "hardware-support", "science"] categories = ["embedded", "no-std", "hardware-support", "science"]
@ -34,11 +35,11 @@ log = { version = "0.4", features = ["max_level_trace", "release_max_level_info"
rtt-target = { version = "0.3", features = ["cortex-m"] } rtt-target = { version = "0.3", features = ["cortex-m"] }
serde = { version = "1.0", features = ["derive"], default-features = false } serde = { version = "1.0", features = ["derive"], default-features = false }
heapless = { version = "0.7", features = ["serde"] } heapless = { version = "0.7", features = ["serde"] }
cortex-m-rtic = "0.5.6" cortex-m-rtic = "0.5.7"
embedded-hal = "0.2.5" embedded-hal = "0.2.5"
nb = "1.0.0" nb = "1.0.0"
asm-delay = "0.9.0" asm-delay = "0.9.0"
num_enum = { version = "0.5.1", default-features = false } num_enum = { version = "0.5.2", default-features = false }
paste = "1" paste = "1"
dsp = { path = "dsp" } dsp = { path = "dsp" }
ad9959 = { path = "ad9959" } ad9959 = { path = "ad9959" }
@ -70,7 +71,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

@ -4,88 +4,13 @@
# Stabilizer Firmware # Stabilizer Firmware
## Hardware
[![Hardware](https://github.com/sinara-hw/Stabilizer/wiki/Stabilizer_v1.0_top_small.jpg)](https://github.com/sinara-hw/Stabilizer)
## Applications ## 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. Check out the [Documentation](https://quartiq.de/stabilizer) for more information on usage,
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). configuration, and development.
An application can compose and configure these hardware and software components to implement different use cases.
Several applications are provides by default:
### Dual-IIR ## Hardware
![Flow diagram](stabilizer_pid.svg) [![Stabilizer](https://github.com/sinara-hw/Stabilizer/wiki/Stabilizer_v1.0_top_small.jpg)](https://github.com/sinara-hw/Stabilizer)
* dual channel [![Pounder](https://user-images.githubusercontent.com/1338946/125936814-3664aa2d-a530-4c85-9393-999a7173424e.png)](https://github.com/sinara-hw/Pounder/wiki)
* 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

4
docs/Gemfile Normal file
View File

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

102
docs/Gemfile.lock Normal file
View File

@ -0,0 +1,102 @@
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)
ethon (0.14.0)
ffi (>= 1.15.0)
eventmachine (1.2.7-x64-mingw32)
ffi (1.15.3-x64-mingw32)
forwardable-extended (2.6.0)
html-proofer (3.19.2)
addressable (~> 2.3)
mercenary (~> 0.3)
nokogumbo (~> 2.0)
parallel (~> 1.3)
rainbow (~> 3.0)
typhoeus (~> 1.3)
yell (~> 2.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)
nokogiri (1.11.7-x64-mingw32)
racc (~> 1.4)
nokogumbo (2.0.5)
nokogiri (~> 1.8, >= 1.8.4)
parallel (1.20.1)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
racc (1.5.2)
rainbow (3.0.0)
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)
typhoeus (1.4.0)
ethon (>= 0.9.0)
unicode-display_width (1.7.0)
yell (2.2.2)
PLATFORMS
x64-mingw32
DEPENDENCIES
html-proofer
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/stabilizer/` 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 --no-deps -p dsp -p miniconf -p stabilizer -p ad9959
cp -r target/thumbv7em-none-eabihf/doc docs/firmware
```

35
docs/Rakefile Normal file
View File

@ -0,0 +1,35 @@
# Rakefile taken from:
# https://seankilleen.com/2019/09/how-to-check-your-jekyll-based-blog-for-dead-links/
# Ensures we have the html-proofer library available to use
require 'html-proofer'
# The function that will run the proofer, so that we can re-use it between our two rake tasks
def run_htmlproofer()
options = {
# Assumes html file extensions
assume_extension: true,
file_ignore: [ /stabilizer\/firmware\/.*/ ],
url_ignore: [ /quartiq.de\/stabilizer/ ],
# The options for the curl library that's used.
:typhoeus => {
# This will stop you from getting errors when certs can't be parsed, which doesn't matter in this case.
:ssl_verifypeer => false
},
# Won't fail for local links
allow_hash_href: true,
}
# Calls html-proofer and uses the Jekyll _site folder
HTMLProofer.check_directory("./_site", options).run
end
task :test do
run_htmlproofer()
end
task :build do
sh "bundle exec jekyll build -d _site/stabilizer"
end

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

57
docs/index.md Normal file
View File

@ -0,0 +1,57 @@
---
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 and contributors.
## Hardware
The Stabilizer hardware is managed via a [separate repository](https://github.com/sinara-hw/Stabilizer).
Some information about the hardware is gathered in the [Stabilizer wiki](https://github.com/sinara-hw/Stabilizer/wiki). More detailed data, measurements, discussions, and tests have been posted in the [Stabilizer issue tracker](https://github.com/sinara-hw/Stabilizer/issues?q=is%3Aissue).
[![Hardware](https://github.com/sinara-hw/Stabilizer/wiki/Stabilizer_v1.0_top_small.jpg)](https://github.com/sinara-hw/Stabilizer)
Stabilizer can be extended and coupled with a mezzanine board. One such mezzanine is the DDS upconversion/downconversion frontend Pounder. The Pounder hardware is managed via a [separate repository](https://github.com/sinara-hw/Pounder), again with [wiki](https://github.com/sinara-hw/Pounder/wiki) and [issue tracker](https://github.com/sinara-hw/Pounder/issues?q=is%3Aissue).
## 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) in the [DSP crate]({{site.baseurl}}/firmware/dsp/index.html).
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 | Description |
| :---: | :---- |
| [`dual-iir`]({{site.baseurl}}/firmware/dual_iir/index.html) | Two channel biquad IIR filter |
| [`lockin`]({{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,157 @@
---
title: Getting Started
layout: default
permalink: /getting-started
nav_order: 2
---
## Table of Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
---
# Getting Started
{: .no_toc }
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.
The following sections will walk you through completing each of these steps.
# Update 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:
* 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)
```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.

97
docs/pages/usage.md Normal file
View File

@ -0,0 +1,97 @@
---
title: Usage
layout: default
nav_order: 4
permalink: /usage
---
## 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.
### 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 10.34.16.10 dt/sinara/dual-iir/00-11-22-33-44-55 stream_target='{"ip": [10, 34, 16, 123], "port": 4000}'
Where `10.34.16.10` is the MQTT broker address that matches the one configured in the source code and `10.34.16.123` and `4000` are the desire stream target IP and port.
```
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]({{site.baseurl}}/#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.
## 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]({{site.baseurl}}/#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]({{site.baseurl}}/#applications) for more information.

View File

@ -1,6 +1,7 @@
[package] [package]
name = "dsp" name = "dsp"
version = "0.1.0" version = "0.1.0"
resolver = "2"
authors = ["Robert Jördens <rj@quartiq.de>"] authors = ["Robert Jördens <rj@quartiq.de>"]
edition = "2018" edition = "2018"

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,19 @@ 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": y_offset, "y_min": y_min, "y_max": y_max, "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>
#[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]
@ -35,11 +62,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,
signal_generator: [signal_generator::BasicConfig; 2], signal_generator: [signal_generator::BasicConfig; 2],
} }

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},
@ -32,35 +59,118 @@ use stabilizer::{
#[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,
} }
@ -123,8 +233,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.
@ -213,8 +323,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,
) )
} }
@ -222,7 +331,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 100 MHz.
///
/// ## Example
/// With a value of 7, this corresponds to 2^7 = 128 ticks. Each tick of the 100 MHz timer requires
/// 10ns.
///
/// Sampling Period = 10ns * 128 = 1.28 us
/// Sampling Frequency = 781.25 KHz
///
/// Or more succinctly:
/// `F_s = 100 MHz / (2 ^ ADC_SAMPLE_TICKS_LOG2)`
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7;
/// Sample Batch Sizing
///
/// The sample batch size defines how many samples are collected before the DSP routines are
/// executed.
///
/// # Note
/// Smaller batch sizes result in less input -> output latency, but come at the cost of reduced
/// maximum sampling frequency.
///
/// # Units
/// The units of the batch size are specified logarithmically.
///
/// ## Example
/// With a value of 3, the number of samples per batch is 2^3 = 8.
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;

View File

@ -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,10 +58,8 @@ 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(),
); );
Self { Self {

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,9 +48,14 @@ pub struct TelemetryBuffer {
/// overhead. /// overhead.
#[derive(Serialize)] #[derive(Serialize)]
pub struct Telemetry { pub struct Telemetry {
adcs: [f32; 2], /// Most recent input voltage measurement.
dacs: [f32; 2], pub adcs: [f32; 2],
digital_inputs: [bool; 2],
/// Most recent output voltage.
pub dacs: [f32; 2],
/// Most recent digital input assertion state.
pub digital_inputs: [bool; 2],
} }
impl Default for TelemetryBuffer { impl Default for TelemetryBuffer {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 130 B