Merge branch 'master' into feature/mqtt-convert

master
Ryan Summers 2021-02-17 11:44:10 +01:00
commit 9e1f4a864c
26 changed files with 432 additions and 335 deletions

6
.github/bors.toml vendored
View File

@ -1,3 +1,7 @@
block_labels = [ "S-blocked" ]
delete_merged_branches = true
status = ["ci"]
status = [
"style",
"test (stable)",
"compile (stable)",
]

View File

@ -16,11 +16,9 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: thumbv7em-none-eabihf
override: true
components: rustfmt, clippy
- name: cargo fmt --check
uses: actions-rs/cargo@v1
with:
@ -37,59 +35,29 @@ jobs:
compile:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
strategy:
matrix:
toolchain:
- stable
bin:
- dual-iir
- lockin-internal
- lockin-external
features:
- ''
toolchain: [stable]
features: ['']
include:
- toolchain: beta
bin: dual-iir
features: ''
- toolchain: stable
bin: lockin-internal
features: pounder_v1_1
- toolchain: nightly
features: nightly
steps:
- uses: actions/checkout@v2
- name: Install Rust ${{ matrix.toolchain }}
uses: actions-rs/toolchain@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
target: thumbv7em-none-eabihf
override: true
components: llvm-tools-preview
- name: cargo build release
uses: actions-rs/cargo@v1
- uses: actions-rs/cargo@v1
with:
command: build
args: --release --features "${{ matrix.features }}" --bin ${{ matrix.bin }}
- name: cargo-binutils
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-binutils
- name: cargo size
uses: actions-rs/cargo@v1
with:
command: size
args: --release --features "${{ matrix.features }}" --bin ${{ matrix.bin }}
- name: cargo objcopy
uses: actions-rs/cargo@v1
with:
command: objcopy
args: --release --features "${{ matrix.features }}" --bin ${{ matrix.bin }} --verbose -- -O binary ${{ matrix.bin }}-release.bin
- uses: actions/upload-artifact@v2
if: ${{ matrix.toolchain == 'stable' && matrix.features == '' }}
with:
name: stabilizer_${{ matrix.bin }}
path: |
target/*/release/${{ matrix.bin }}
${{ matrix.bin }}-release.bin
args: --release --features "${{ matrix.features }}"
test:
runs-on: ubuntu-latest
@ -100,8 +68,7 @@ jobs:
- beta
steps:
- uses: actions/checkout@v2
- name: Install Rust ${{ matrix.toolchain }}
uses: actions-rs/toolchain@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
- name: cargo test
@ -114,28 +81,3 @@ jobs:
with:
command: bench
args: --package dsp --target=x86_64-unknown-linux-gnu
# Tell bors about it
# https://github.com/rtic-rs/cortex-m-rtic/blob/8a4f9c6b8ae91bebeea0791680f89375a78bffc6/.github/workflows/build.yml#L566-L603
ci-success:
name: ci
if: github.event_name == 'push' && success()
needs:
- style
- compile
- test
runs-on: ubuntu-latest
steps:
- name: Mark the job as a success
run: exit 0
ci-failure:
name: ci
if: github.event_name == 'push' && !success()
needs:
- style
- compile
- test
runs-on: ubuntu-latest
steps:
- name: Mark the job as a failure
run: exit 1

45
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Release binaries
on:
push:
tags: ['v*']
env:
CARGO_TERM_COLOR: always
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: thumbv7em-none-eabihf
override: true
- uses: actions-rs/cargo@v1
with:
command: build
args: --release --features ""
- run: >
zip bin.zip
target/*/release/dual-iir
target/*/release/lockin-external
target/*/release/lockin-internal
- id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: false
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin.zip
asset_name: stabilizer-${{ github.ref }}.zip
asset_content_type: application/zip

102
Cargo.lock generated
View File

@ -98,9 +98,9 @@ checksum = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d"
[[package]]
name = "bstr"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
dependencies = [
"lazy_static",
"memchr",
@ -110,15 +110,15 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.4.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byteorder"
version = "1.3.4"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "cast"
@ -148,9 +148,9 @@ dependencies = [
[[package]]
name = "const_fn"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
[[package]]
name = "cortex-m"
@ -351,6 +351,7 @@ dependencies = [
[[package]]
name = "derive_stringset"
version = "0.1.0"
source = "git+https://github.com/vertigo-designs/miniconf.git?branch=develop#256098cafc6db93b456b5014c8ef1b835b5e239d"
dependencies = [
"proc-macro2",
"quote",
@ -362,6 +363,7 @@ name = "dsp"
version = "0.1.0"
dependencies = [
"criterion",
"generic-array 0.14.4",
"libm",
"ndarray",
"rand",
@ -466,9 +468,9 @@ dependencies = [
[[package]]
name = "half"
version = "1.6.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hash32"
@ -500,18 +502,18 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.17"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.6.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
dependencies = [
"autocfg",
"hashbrown",
@ -537,15 +539,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.46"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
dependencies = [
"wasm-bindgen",
]
@ -558,9 +560,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.81"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]]
name = "libm"
@ -618,6 +620,7 @@ dependencies = [
[[package]]
name = "miniconf"
version = "0.1.0"
source = "git+https://github.com/vertigo-designs/miniconf.git?branch=develop#256098cafc6db93b456b5014c8ef1b835b5e239d"
dependencies = [
"derive_stringset",
"heapless",
@ -628,8 +631,9 @@ dependencies = [
[[package]]
name = "minimq"
version = "0.1.0"
source = "git+https://github.com/quartiq/minimq.git#83e946544cebd09c9dd07ff1271be639138ec1ce"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5e626690b6f62e15710cf9815e5ca25ee54084899298c100a14b2504c80a46"
dependencies = [
"bit_field",
"embedded-nal",
@ -783,9 +787,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.8"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
@ -820,9 +824,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom",
]
@ -869,9 +873,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
"regex-syntax",
]
@ -887,9 +891,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.21"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]]
name = "rtic-core"
@ -1005,9 +1009,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.60"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
dependencies = [
"itoa",
"ryu",
@ -1082,7 +1086,7 @@ dependencies = [
[[package]]
name = "stm32h7xx-hal"
version = "0.8.0"
source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#2b8a04caac566a8560f400ddd6503508f78bea77"
source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=master#08231e334a11236fe556668ac19cb1c214da2406"
dependencies = [
"bare-metal 1.0.0",
"cast",
@ -1119,9 +1123,9 @@ dependencies = [
[[package]]
name = "tinytemplate"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74"
dependencies = [
"serde",
"serde_json",
@ -1147,9 +1151,9 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vcell"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
@ -1191,9 +1195,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -1201,9 +1205,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
dependencies = [
"bumpalo",
"lazy_static",
@ -1216,9 +1220,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1226,9 +1230,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [
"proc-macro2",
"quote",
@ -1239,15 +1243,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
[[package]]
name = "web-sys"
version = "0.3.46"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
dependencies = [
"js-sys",
"wasm-bindgen",

View File

@ -47,7 +47,7 @@ ad9959 = { path = "ad9959" }
[dependencies.miniconf]
git = "https://github.com/vertigo-designs/miniconf.git"
branch = "feature/mqtt-interface"
branch = "develop"
[dependencies.mcp23017]
git = "https://github.com/mrd0ll4r/mcp23017.git"
@ -59,7 +59,7 @@ branch = "main"
[dependencies.stm32h7xx-hal]
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
git = "https://github.com/stm32-rs/stm32h7xx-hal"
branch = "dma"
branch = "master"
[features]
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]

View File

@ -1 +1 @@
"0ysy8fg6kbblhmjyavq6pg77n21fcygwc0hvidmg2yywkhgdi348"
"138kpxzxs73zhmd4xi5kw3fddb05gac4mpngizm01831n1ycyhl0"

View File

@ -8,6 +8,7 @@ edition = "2018"
libm = "0.2.1"
serde = { version = "1.0", features = ["derive"], default-features = false }
serde-json-core = "0.1"
generic-array = "0.14"
[dev-dependencies]
criterion = "0.3"

View File

@ -1,8 +1,19 @@
use core::ops::Mul;
use super::{atan2, cossin};
#[derive(Copy, Clone, Default, PartialEq, Debug)]
pub struct Complex<T>(pub T, pub T);
impl<T: Copy> Complex<T> {
pub fn map<F>(&self, func: F) -> Self
where
F: Fn(T) -> T,
{
Complex(func(self.0), func(self.1))
}
}
impl Complex<i32> {
/// Return a Complex on the unit circle given an angle.
///
@ -21,19 +32,43 @@ impl Complex<i32> {
/// Return the absolute square (the squared magnitude).
///
/// Note: Normalization is `1 << 31`, i.e. Q0.31.
/// Note: Normalization is `1 << 32`, i.e. U0.32.
///
/// Note(panic): This will panic for `Complex(i32::MIN, i32::MIN)`
///
/// Example:
///
/// ```
/// use dsp::Complex;
/// assert_eq!(Complex(i32::MAX, 0).abs_sqr(), i32::MAX - 1);
/// assert_eq!(Complex(i32::MIN + 1, 0).abs_sqr(), i32::MAX - 1);
/// assert_eq!(Complex(i32::MIN, 0).abs_sqr(), 1 << 31);
/// assert_eq!(Complex(i32::MAX, i32::MAX).abs_sqr(), u32::MAX - 3);
/// ```
pub fn abs_sqr(&self) -> i32 {
pub fn abs_sqr(&self) -> u32 {
(((self.0 as i64) * (self.0 as i64)
+ (self.1 as i64) * (self.1 as i64))
>> 31) as i32
>> 31) as u32
}
/// log2(power) re full scale approximation
///
/// TODO: scale up, interpolate
///
/// Panic:
/// This will panic for `Complex(i32::MIN, i32::MIN)`
///
/// Example:
///
/// ```
/// use dsp::Complex;
/// assert_eq!(Complex(i32::MAX, i32::MAX).log2(), -1);
/// assert_eq!(Complex(i32::MAX, 0).log2(), -2);
/// assert_eq!(Complex(1, 0).log2(), -63);
/// assert_eq!(Complex(0, 0).log2(), -64);
/// ```
pub fn log2(&self) -> i32 {
let a = (self.0 as i64) * (self.0 as i64)
+ (self.1 as i64) * (self.1 as i64);
-(a.leading_zeros() as i32)
}
/// Return the angle.
@ -44,14 +79,51 @@ impl Complex<i32> {
///
/// ```
/// use dsp::Complex;
/// assert_eq!(Complex(i32::MAX, 0).arg(), 0);
/// assert_eq!(Complex(1, 0).arg(), 0);
/// assert_eq!(Complex(-i32::MAX, 1).arg(), i32::MAX);
/// assert_eq!(Complex(-i32::MAX, -1).arg(), -i32::MAX);
/// assert_eq!(Complex(0, -i32::MAX).arg(), -i32::MAX >> 1);
/// assert_eq!(Complex(0, i32::MAX).arg(), (i32::MAX >> 1) + 1);
/// assert_eq!(Complex(i32::MAX, i32::MAX).arg(), (i32::MAX >> 2) + 1);
/// assert_eq!(Complex(0, -1).arg(), -i32::MAX >> 1);
/// assert_eq!(Complex(0, 1).arg(), (i32::MAX >> 1) + 1);
/// assert_eq!(Complex(1, 1).arg(), (i32::MAX >> 2) + 1);
/// ```
pub fn arg(&self) -> i32 {
atan2(self.1, self.0)
}
}
impl Mul for Complex<i32> {
type Output = Self;
fn mul(self, other: Self) -> Self {
let a = self.0 as i64;
let b = self.1 as i64;
let c = other.0 as i64;
let d = other.1 as i64;
Complex(
((a * c - b * d + (1 << 31)) >> 32) as i32,
((b * c + a * d + (1 << 31)) >> 32) as i32,
)
}
}
impl Mul<i32> for Complex<i32> {
type Output = Self;
fn mul(self, other: i32) -> Self {
Complex(
((other as i64 * self.0 as i64 + (1 << 31)) >> 32) as i32,
((other as i64 * self.1 as i64 + (1 << 31)) >> 32) as i32,
)
}
}
impl Mul<i16> for Complex<i32> {
type Output = Self;
fn mul(self, other: i16) -> Self {
Complex(
(other as i32 * (self.0 >> 16) + (1 << 15)) >> 16,
(other as i32 * (self.1 >> 16) + (1 << 15)) >> 16,
)
}
}

View File

@ -1,4 +1,4 @@
use core::f32::consts::PI;
use core::f64::consts::PI;
use serde::{Deserialize, Serialize};
/// Generic vector for integer IIR filter.
@ -19,7 +19,7 @@ impl Vec5 {
///
/// # Returns
/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
pub fn lowpass(f: f32, q: f32, k: f32) -> Self {
pub fn lowpass(f: f64, q: f64, k: f64) -> Self {
// 3rd order Taylor approximation of sin and cos.
let f = f * 2. * PI;
let f2 = f * f * 0.5;
@ -27,10 +27,10 @@ impl Vec5 {
let fsin = f * (1. - f2 / 3.);
let alpha = fsin / (2. * q);
// IIR uses Q2.30 fixed point
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32;
let b0 = (k / 2. * (1. - fcos) / a0) as _;
let a1 = (2. * fcos / a0) as _;
let a2 = ((alpha - 1.) / a0) as _;
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64;
let b0 = (k / 2. * (1. - fcos) / a0 + 0.5) as _;
let a1 = (2. * fcos / a0 + 0.5) as _;
let a2 = ((alpha - 1.) / a0 + 0.5) as _;
Self([b0, 2 * b0, b0, a1, a2])
}
@ -97,7 +97,7 @@ mod test {
#[test]
fn lowpass_gen() {
let ba = Vec5::lowpass(1e-3, 1. / 2f32.sqrt(), 2.);
let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2.);
println!("{:?}", ba.0);
}
}

View File

@ -87,6 +87,7 @@ mod cossin;
pub mod iir;
pub mod iir_int;
pub mod lockin;
pub mod lowpass;
pub mod pll;
pub mod rpll;
pub mod unwrap;

View File

@ -1,41 +1,26 @@
use super::{
iir_int::{Vec5, IIR},
Complex,
};
use serde::{Deserialize, Serialize};
use super::{lowpass::Lowpass, Complex};
use generic_array::typenum::U2;
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
#[derive(Clone, Default)]
pub struct Lockin {
iir: IIR,
state: [Vec5; 2],
state: [Lowpass<U2>; 2],
}
impl Lockin {
/// Create a new Lockin with given IIR coefficients.
pub fn new(ba: Vec5) -> Self {
Self {
iir: IIR { ba },
state: [Vec5::default(); 2],
}
}
/// Update the lockin with a sample taken at a given phase.
pub fn update(&mut self, sample: i32, phase: i32) -> Complex<i32> {
/// The lowpass has a gain of `1 << k`.
pub fn update(&mut self, sample: i16, phase: i32, k: u8) -> Complex<i32> {
// Get the LO signal for demodulation.
let lo = Complex::from_angle(phase);
// Mix with the LO signal, filter with the IIR lowpass,
// Mix with the LO signal
let mix = lo * sample;
// Filter with the IIR lowpass,
// return IQ (in-phase and quadrature) data.
// Note: 32x32 -> 64 bit multiplications are pretty much free.
Complex(
self.iir.update(
&mut self.state[0],
((sample as i64 * lo.0 as i64) >> 32) as _,
),
self.iir.update(
&mut self.state[1],
((sample as i64 * lo.1 as i64) >> 32) as _,
),
self.state[0].update(mix.0, k),
self.state[1].update(mix.1, k),
)
}
}

35
dsp/src/lowpass.rs Normal file
View File

@ -0,0 +1,35 @@
use generic_array::{ArrayLength, GenericArray};
/// Arbitrary order, high dynamic range, wide coefficient range,
/// lowpass filter implementation. DC gain is 1.
///
/// Type argument N is the filter order.
#[derive(Clone, Default)]
pub struct Lowpass<N: ArrayLength<i32>> {
// IIR state storage
y: GenericArray<i32, N>,
}
impl<N: ArrayLength<i32>> Lowpass<N> {
/// Update the filter with a new sample.
///
/// # Args
/// * `x`: Input data, needs `k` bits headroom.
/// * `k`: Log2 time constant, 0..31.
///
/// # Return
/// Filtered output y, with gain of `1 << k`.
pub fn update(&mut self, x: i32, k: u8) -> i32 {
debug_assert!(k & 31 == k);
// This is an unrolled and optimized first-order IIR loop
// that works for all possible time constants.
// Note DF-II and the zeros at Nyquist.
let mut x = x << k;
for y in self.y.iter_mut() {
let dy = (x - *y + (1 << (k - 1))) >> k;
*y += dy;
x = *y - (dy >> 1);
}
x
}
}

View File

@ -94,7 +94,6 @@ mod test {
struct Harness {
rpll: RPLL,
dt2: u8,
shift_frequency: u8,
shift_phase: u8,
noise: i32,
@ -109,7 +108,6 @@ mod test {
fn default() -> Self {
Self {
rpll: RPLL::new(8),
dt2: 8,
shift_frequency: 9,
shift_phase: 8,
noise: 0,
@ -122,7 +120,7 @@ mod test {
}
fn run(&mut self, n: usize) -> (Vec<f32>, Vec<f32>) {
assert!(self.period >= 1 << self.dt2);
assert!(self.period >= 1 << self.rpll.dt2);
assert!(self.period < 1 << self.shift_frequency);
assert!(self.period < 1 << self.shift_phase + 1);
@ -130,7 +128,7 @@ mod test {
let mut f = Vec::<f32>::new();
for _ in 0..n {
let timestamp = if self.time - self.next_noisy >= 0 {
assert!(self.time - self.next_noisy < 1 << self.dt2);
assert!(self.time - self.next_noisy < 1 << self.rpll.dt2);
self.next = self.next.wrapping_add(self.period);
let timestamp = self.next_noisy;
let p_noise = self.rng.gen_range(-self.noise..=self.noise);
@ -151,23 +149,23 @@ mod test {
// phase error
y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32));
let p_ref = 1 << 32 + self.dt2;
let p_ref = 1 << 32 + self.rpll.dt2;
let p_sig = fi as u64 * self.period as u64;
// relative frequency error
f.push(
p_sig.wrapping_sub(p_ref) as i64 as f32
/ 2f32.powi(32 + self.dt2 as i32),
/ 2f32.powi(32 + self.rpll.dt2 as i32),
);
// advance time
self.time = self.time.wrapping_add(1 << self.dt2);
self.time = self.time.wrapping_add(1 << self.rpll.dt2);
}
(y, f)
}
fn measure(&mut self, n: usize, limits: [f32; 4]) {
let t_settle = (1 << self.shift_frequency - self.dt2 + 4)
+ (1 << self.shift_phase - self.dt2 + 4);
let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4)
+ (1 << self.shift_phase - self.rpll.dt2 + 4);
self.run(t_settle);
let (y, f) = self.run(n);
@ -268,4 +266,18 @@ mod test {
h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]);
}
#[test]
fn batch_fast_narrow() {
let mut h = Harness::default();
h.rpll.dt2 = 8 + 3;
h.period = 2431;
h.next = 35281;
h.next_noisy = h.next;
h.noise = 100;
h.shift_frequency = 23;
h.shift_phase = 23;
h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]);
}
}

View File

@ -1,7 +1,6 @@
#![deny(warnings)]
#![no_std]
#![no_main]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
use stm32h7xx_hal as hal;
@ -11,6 +10,7 @@ use stabilizer::hardware;
use miniconf::{
embedded_nal::{IpAddr, Ipv4Addr},
minimq,
MqttInterface, StringSet,
};
use serde::Deserialize;
@ -44,7 +44,7 @@ const APP: () = {
afes: (AFE0, AFE1),
adcs: (Adc0Input, Adc1Input),
dacs: (Dac0Output, Dac1Output),
mqtt_interface: MqttInterface<Settings, NetworkStack>,
mqtt_interface: MqttInterface<Settings, NetworkStack, minimq::consts::U256>,
// Format: iir_state[ch][cascade-no][coeff]
#[init([[iir::Vec5([0.; 5]); IIR_CASCADE_LENGTH]; 2])]
@ -58,14 +58,19 @@ const APP: () = {
// Configure the microcontroller
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
let broker = IpAddr::V4(Ipv4Addr::new(10, 34, 16, 1));
let mqtt_interface = MqttInterface::new(
stabilizer.net.stack,
"stabilizer",
broker,
Settings::new(),
)
.unwrap();
let mqtt_interface = {
let mqtt_client = {
let broker = IpAddr::V4(Ipv4Addr::new(10, 34, 16, 1));
minimq::MqttClient::new(broker, "stabilizer", stabilizer.net.stack).unwrap()
};
MqttInterface::new(
mqtt_client,
"stabilizer",
Settings::new(),
)
.unwrap()
};
// Enable ADC/DAC events
stabilizer.adcs.0.start();

View File

@ -1,30 +1,26 @@
#![deny(warnings)]
#![no_std]
#![no_main]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
use stm32h7xx_hal as hal;
use rtic::cyccnt::{Instant, U32Ext};
use stabilizer::{hardware, ADC_SAMPLE_TICKS_LOG2, SAMPLE_BUFFER_SIZE_LOG2};
use miniconf::{
embedded_nal::{IpAddr, Ipv4Addr},
minimq,
MqttInterface, StringSet,
};
use serde::Deserialize;
use dsp::{iir, iir_int, lockin::Lockin, rpll::RPLL, Accu};
use serde::Deserialize;
use stabilizer::{hardware, hardware::design_parameters};
use dsp::{lockin::Lockin, rpll::RPLL, Accu};
use hardware::{
Adc0Input, Adc1Input, Dac0Output, Dac1Output, InputStamper, AFE0, AFE1,
};
const SCALE: f32 = i16::MAX as _;
// The number of cascaded IIR biquads per channel. Select 1 or 2!
const IIR_CASCADE_LENGTH: usize = 1;
#[derive(Deserialize, StringSet)]
pub struct Settings {
}
@ -42,13 +38,7 @@ const APP: () = {
afes: (AFE0, AFE1),
adcs: (Adc0Input, Adc1Input),
dacs: (Dac0Output, Dac1Output),
mqtt_interface: MqttInterface<Settings, hardware::NetworkStack>,
// Format: iir_state[ch][cascade-no][coeff]
#[init([[iir::Vec5([0.; 5]); IIR_CASCADE_LENGTH]; 2])]
iir_state: [[iir::Vec5; IIR_CASCADE_LENGTH]; 2],
#[init([[iir::IIR::new(1./(1 << 16) as f32, -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2])]
iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2],
mqtt_interface: MqttInterface<Settings, hardware::NetworkStack, minimq::consts::U256>,
timestamper: InputStamper,
pll: RPLL,
@ -60,19 +50,23 @@ const APP: () = {
// Configure the microcontroller
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
let broker = IpAddr::V4(Ipv4Addr::new(10, 34, 16, 1));
let mqtt_interface = MqttInterface::new(
stabilizer.net.stack,
"stabilizer/lockin",
broker,
Settings::new(),
)
.unwrap();
let mqtt_interface = {
let mqtt_client = {
let broker = IpAddr::V4(Ipv4Addr::new(10, 34, 16, 1));
minimq::MqttClient::new(broker, "stabilizer", stabilizer.net.stack).unwrap()
};
let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + SAMPLE_BUFFER_SIZE_LOG2);
MqttInterface::new(
mqtt_client,
"stabilizer",
Settings::new(),
)
.unwrap()
};
let lockin = Lockin::new(
iir_int::Vec5::lowpass(1e-3, 0.707, 2.), // TODO: expose
let pll = RPLL::new(
design_parameters::ADC_SAMPLE_TICKS_LOG2
+ design_parameters::SAMPLE_BUFFER_SIZE_LOG2,
);
// Enable ADC/DAC events
@ -87,6 +81,9 @@ const APP: () = {
// Start sampling ADCs.
stabilizer.adc_dac_timer.start();
// Enable the timestamper.
stabilizer.timestamper.start();
init::LateResources {
mqtt_interface,
afes: stabilizer.afes,
@ -95,7 +92,7 @@ const APP: () = {
timestamper: stabilizer.timestamper,
pll,
lockin,
lockin: Lockin::default(),
}
}
@ -106,7 +103,7 @@ const APP: () = {
/// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal.
/// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale.
/// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available.
#[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch, lockin, timestamper, pll], priority=2)]
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll], priority=2)]
fn process(c: process::Context) {
let adc_samples = [
c.resources.adcs.0.acquire_buffer(),
@ -118,30 +115,34 @@ const APP: () = {
c.resources.dacs.1.acquire_buffer(),
];
let iir_ch = c.resources.iir_ch;
let iir_state = c.resources.iir_state;
let lockin = c.resources.lockin;
let timestamp = c
.resources
.timestamper
.latest_timestamp()
.unwrap_or_else(|t| t) // Ignore timer capture overflows.
.unwrap_or(None) // Ignore data from timer capture overflows.
.map(|t| t as i32);
let (pll_phase, pll_frequency) = c.resources.pll.update(
timestamp,
22, // frequency settling time (log2 counter cycles), TODO: expose
22, // phase settling time, TODO: expose
21, // frequency settling time (log2 counter cycles), TODO: expose
21, // phase settling time, TODO: expose
);
// Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
let harmonic: i32 = -1; // TODO: expose
// Demodulation LO phase offset
// Demodulation LO phase offset
let phase_offset: i32 = 0; // TODO: expose
// Log2 lowpass time constant
let time_constant: u8 = 6; // TODO: expose
let sample_frequency = ((pll_frequency
// .wrapping_add(1 << SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias
>> SAMPLE_BUFFER_SIZE_LOG2) as i32)
// half-up rounding bias
// .wrapping_add(1 << design_parameters::SAMPLE_BUFFER_SIZE_LOG2 - 1)
>> design_parameters::SAMPLE_BUFFER_SIZE_LOG2)
as i32)
.wrapping_mul(harmonic);
let sample_phase =
phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic));
@ -151,39 +152,23 @@ const APP: () = {
.zip(Accu::new(sample_phase, sample_frequency))
// Convert to signed, MSB align the ADC sample.
.map(|(&sample, phase)| {
lockin.update((sample as i16 as i32) << 16, phase)
lockin.update(sample as i16, phase, time_constant)
})
.last()
.unwrap();
// convert i/q to power/phase,
let power_phase = true; // TODO: expose
let mut output = if power_phase {
let conf = "frequency_discriminator";
let output = match conf {
// Convert from IQ to power and phase.
[output.abs_sqr() as _, output.arg() as _]
} else {
[output.0 as _, output.1 as _]
"power_phase" => [(output.log2() << 24) as _, output.arg()],
"frequency_discriminator" => [pll_frequency as _, output.arg()],
_ => [output.0, output.1],
};
// Filter power and phase through IIR filters.
// Note: Normalization to be done in filters. Phase will wrap happily.
for j in 0..iir_state[0].len() {
for k in 0..output.len() {
output[k] =
iir_ch[k][j].update(&mut iir_state[k][j], output[k]);
}
}
// Note(unsafe): range clipping to i16 is ensured by IIR filters above.
// Convert to DAC data.
for i in 0..dac_samples[0].len() {
unsafe {
dac_samples[0][i] =
output[0].to_int_unchecked::<i16>() as u16 ^ 0x8000;
dac_samples[1][i] =
output[1].to_int_unchecked::<i16>() as u16 ^ 0x8000;
}
dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000;
dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000;
}
}
@ -225,7 +210,7 @@ const APP: () = {
}
}
#[task(priority = 1, resources=[mqtt_interface, afes, iir_ch])]
#[task(priority = 1, resources=[mqtt_interface, afes])]
fn settings_update(c: settings_update::Context) {
let _settings = &c.resources.mqtt_interface.settings;
//c.resources.iir_ch.lock(|iir| *iir = settings.iir);

View File

@ -1,24 +1,23 @@
#![deny(warnings)]
#![no_std]
#![no_main]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
use dsp::{iir_int, lockin::Lockin, Accu};
use dsp::{lockin::Lockin, Accu};
use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1};
use stabilizer::{hardware, SAMPLE_BUFFER_SIZE, SAMPLE_BUFFER_SIZE_LOG2};
use stabilizer::{hardware, hardware::design_parameters};
// A constant sinusoid to send on the DAC output.
// Full-scale gives a +/- 10V amplitude waveform. Scale it down to give +/- 1V.
const ONE: i16 = (0.1 * u16::MAX as f32) as _;
const SQRT2: i16 = (ONE as f32 * 0.707) as _;
const DAC_SEQUENCE: [i16; SAMPLE_BUFFER_SIZE] =
const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] =
[ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2];
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
const APP: () = {
struct Resources {
afes: (AFE0, AFE1),
adc1: Adc1Input,
adc: Adc1Input,
dacs: (Dac0Output, Dac1Output),
lockin: Lockin,
@ -29,10 +28,6 @@ const APP: () = {
// Configure the microcontroller
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
let lockin = Lockin::new(
iir_int::Vec5::lowpass(1e-3, 0.707, 2.), // TODO: expose
);
// Enable ADC/DAC events
stabilizer.adcs.1.start();
stabilizer.dacs.0.start();
@ -42,9 +37,9 @@ const APP: () = {
stabilizer.adc_dac_timer.start();
init::LateResources {
lockin,
lockin: Lockin::default(),
afes: stabilizer.afes,
adc1: stabilizer.adcs.1,
adc: stabilizer.adcs.1,
dacs: stabilizer.dacs,
}
}
@ -67,10 +62,10 @@ const APP: () = {
/// the same time bounds, meeting one also means the other is also met.
///
/// TODO: Document
#[task(binds=DMA1_STR4, resources=[adc1, dacs, lockin], priority=2)]
#[task(binds=DMA1_STR4, resources=[adc, dacs, lockin], priority=2)]
fn process(c: process::Context) {
let lockin = c.resources.lockin;
let adc_samples = c.resources.adc1.acquire_buffer();
let adc_samples = c.resources.adc.acquire_buffer();
let dac_samples = [
c.resources.dacs.0.acquire_buffer(),
c.resources.dacs.1.acquire_buffer(),
@ -84,13 +79,18 @@ const APP: () = {
// Reference phase and frequency are known.
let pll_phase = 0;
let pll_frequency = 1i32 << (32 - SAMPLE_BUFFER_SIZE_LOG2);
let pll_frequency =
1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2);
// Harmonic index of the LO: -1 to _de_modulate the fundamental
let harmonic: i32 = -1;
let harmonic: i32 = -1; // TODO: expose
// Demodulation LO phase offset
let phase_offset: i32 = (0.25 * i32::MAX as f32) as i32;
let phase_offset: i32 = (0.25 * i32::MAX as f32) as i32; // TODO: expose
// Log2 lowpass time constant.
let time_constant: u8 = 8;
let sample_frequency = (pll_frequency as i32).wrapping_mul(harmonic);
let sample_phase = phase_offset
.wrapping_add((pll_phase as i32).wrapping_mul(harmonic));
@ -101,24 +101,14 @@ const APP: () = {
.zip(Accu::new(sample_phase, sample_frequency))
// Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter)
.map(|(&sample, phase)| {
lockin.update((sample as i16 as i32) << 16, phase)
lockin.update(sample as i16, phase, time_constant)
})
// Decimate
.last()
.unwrap();
// convert i/q to power/phase,
let power_phase = true; // TODO: expose
let output = if power_phase {
// Convert from IQ to power and phase.
[output.abs_sqr(), output.arg()]
} else {
[output.0, output.1]
};
for value in dac_samples[1].iter_mut() {
*value = (output[1] >> 16) as u16 ^ 0x8000;
*value = (output.arg() >> 16) as u16 ^ 0x8000;
}
}

View File

@ -74,9 +74,9 @@
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
use stm32h7xx_hal as hal;
use crate::SAMPLE_BUFFER_SIZE;
use super::design_parameters::SAMPLE_BUFFER_SIZE;
use super::timers;
use hal::dma::{
config::Priority,
dma::{DMAReq, DmaConfig},

View File

@ -1,14 +1,6 @@
///! Stabilizer hardware configuration
///!
///! This file contains all of the hardware-specific configuration of Stabilizer.
use crate::ADC_SAMPLE_TICKS;
#[cfg(feature = "pounder_v1_1")]
use crate::SAMPLE_BUFFER_SIZE;
#[cfg(feature = "pounder_v1_1")]
use core::convert::TryInto;
use stm32h7xx_hal::{
self as hal,
ethernet::{self, PHY},
@ -149,6 +141,11 @@ pub fn setup(
let dma_streams =
hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
// Early, before the DMA1 peripherals (#272)
#[cfg(feature = "pounder_v1_1")]
let dma2_streams =
hal::dma::dma::StreamsTuple::new(device.DMA2, ccdr.peripheral.DMA2);
// Configure timer 2 to trigger conversions for the ADC
let mut sampling_timer = {
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
@ -164,7 +161,8 @@ pub fn setup(
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
let mut sampling_timer = timers::SamplingTimer::new(timer2);
sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32);
sampling_timer
.set_period_ticks((design_parameters::ADC_SAMPLE_TICKS - 1) as u32);
// The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
// it generates a trigger whenever it is enabled.
@ -188,7 +186,8 @@ pub fn setup(
let mut shadow_sampling_timer =
timers::ShadowSamplingTimer::new(timer3);
shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1);
shadow_sampling_timer
.set_period_ticks(design_parameters::ADC_SAMPLE_TICKS - 1);
// The shadow sampling timer is a slave-mode timer to the sampling timer. It should
// always be in-sync - thus, we configure it to operate in slave mode using "Trigger
@ -639,7 +638,7 @@ pub fn setup(
let ref_clk: hal::time::Hertz =
design_parameters::DDS_REF_CLK.into();
let ad9959 = ad9959::Ad9959::new(
let mut ad9959 = ad9959::Ad9959::new(
qspi_interface,
reset_pin,
&mut io_update,
@ -650,6 +649,8 @@ pub fn setup(
)
.unwrap();
ad9959.self_test().unwrap();
// Return IO_Update
gpiog.pg7 = io_update.into_analog();
@ -762,7 +763,8 @@ pub fn setup(
let sample_frequency = {
let timer_frequency: hal::time::Hertz =
design_parameters::TIMER_FREQUENCY.into();
timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32
timer_frequency.0 as f32
/ design_parameters::ADC_SAMPLE_TICKS as f32
};
let sample_period = 1.0 / sample_frequency;
@ -779,11 +781,6 @@ pub fn setup(
#[cfg(feature = "pounder_v1_1")]
let pounder_stamper = {
let dma2_streams = hal::dma::dma::StreamsTuple::new(
device.DMA2,
ccdr.peripheral.DMA2,
);
let etr_pin = gpioa.pa0.into_alternate_af3();
// The frequency in the constructor is dont-care, as we will modify the period + clock
@ -797,22 +794,13 @@ pub fn setup(
// Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
// output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
// the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
// This is less than fCK_INT/3 of the timer as required for oversampling the trigger.
timestamp_timer.set_external_clock(timers::Prescaler::Div4);
timestamp_timer.start();
// We want the pounder timestamp timer to overflow once per batch.
let tick_ratio = {
let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0
as f32
/ design_parameters::DDS_SYNC_CLK_DIV as f32;
sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32
};
let period = (tick_ratio
* ADC_SAMPLE_TICKS as f32
* SAMPLE_BUFFER_SIZE as f32) as u32
/ 4;
timestamp_timer.set_period_ticks((period - 1).try_into().unwrap());
// Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
// Scale and wrap before or after the PLL.
timestamp_timer.set_period_ticks(u16::MAX);
let tim8_channels = timestamp_timer.channels();
pounder::timestamp::Timestamper::new(

View File

@ -52,9 +52,9 @@
///! served promptly after the transfer completes.
use stm32h7xx_hal as hal;
use crate::SAMPLE_BUFFER_SIZE;
use super::design_parameters::SAMPLE_BUFFER_SIZE;
use super::timers;
use hal::dma::{
dma::{DMAReq, DmaConfig},
traits::TargetAddress,

View File

@ -39,3 +39,13 @@ pub const DDS_SYSTEM_CLK: MegaHertz =
/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
#[allow(dead_code)]
pub const DDS_SYNC_CLK_DIV: u8 = 4;
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
// equal to 10ns per tick.
// 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;
// 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;

View File

@ -44,8 +44,12 @@ impl InputStamper {
) -> Self {
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
// capture source.
let input_capture =
timer_channel.into_input_capture(timers::CaptureTrigger::Input24);
let mut input_capture =
timer_channel.into_input_capture(timers::tim5::CaptureSource4::TI4);
// Do not prescale the input capture signal - require 8 consecutive samples to record an
// incoming event - this prevents spurious glitches from triggering captures.
input_capture.configure_filter(timers::InputFilter::Div1N8);
Self {
capture_channel: input_capture,

View File

@ -11,10 +11,10 @@ mod adc;
mod afe;
mod configuration;
mod dac;
mod design_parameters;
pub mod design_parameters;
mod digital_input_stamper;
mod eeprom;
mod pounder;
pub mod pounder;
mod timers;
pub use adc::{Adc0Input, Adc1Input};

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
mod attenuators;
pub mod attenuators;
mod dds_output;
pub mod hrtimer;
mod rf_power;

View File

@ -26,7 +26,7 @@ use stm32h7xx_hal as hal;
use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer};
use crate::{hardware::timers, SAMPLE_BUFFER_SIZE};
use crate::hardware::{design_parameters::SAMPLE_BUFFER_SIZE, timers};
// Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the
// working data provided to the application. These buffers must exist in a DMA-accessible memory
@ -86,7 +86,7 @@ impl Timestamper {
// The capture channel should capture whenever the trigger input occurs.
let input_capture = capture_channel
.into_input_capture(timers::CaptureTrigger::TriggerInput);
.into_input_capture(timers::tim8::CaptureSource1::TRC);
input_capture.listen_dma();
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.

View File

@ -1,13 +1,14 @@
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
use super::hal;
/// The source of an input capture trigger.
#[allow(dead_code)]
pub enum CaptureTrigger {
Input13 = 0b01,
Input24 = 0b10,
TriggerInput = 0b11,
}
use hal::stm32::{
// TIM1 and TIM8 have identical registers.
tim1 as __tim8,
tim2 as __tim2,
// TIM2 and TIM5 have identical registers.
tim2 as __tim5,
tim3 as __tim3,
};
/// The event that should generate an external trigger from the peripheral.
#[allow(dead_code)]
@ -47,6 +48,13 @@ pub enum SlaveMode {
Trigger = 0b0110,
}
/// Optional input capture preconditioning filter configurations.
#[allow(dead_code)]
pub enum InputFilter {
Div1N1 = 0b0000,
Div1N8 = 0b0011,
}
macro_rules! timer_channels {
($name:ident, $TY:ident, $size:ty) => {
paste::paste! {
@ -225,6 +233,8 @@ macro_rules! timer_channels {
($index:expr, $TY:ty, $ccmrx:expr, $size:ty) => {
paste::paste! {
pub use super::[< __ $TY:lower >]::[< $ccmrx _input >]::[< CC $index S_A>] as [< CaptureSource $index >];
/// A capture/compare channel of the timer.
pub struct [< Channel $index >] {}
@ -267,12 +277,10 @@ macro_rules! timer_channels {
/// # Args
/// * `input` - The input source for the input capture event.
#[allow(dead_code)]
pub fn into_input_capture(self, input: super::CaptureTrigger) -> [< Channel $index InputCapture >]{
pub fn into_input_capture(self, input: [< CaptureSource $index >]) -> [< Channel $index InputCapture >]{
let regs = unsafe { &*<$TY>::ptr() };
// Note(unsafe): The bit configuration is guaranteed to be valid by the
// CaptureTrigger enum definition.
regs.[< $ccmrx _input >]().modify(|_, w| unsafe { w.[< cc $index s>]().bits(input as u8) });
regs.[< $ccmrx _input >]().modify(|_, w| w.[< cc $index s>]().variant(input));
[< Channel $index InputCapture >] {}
}
@ -316,6 +324,9 @@ macro_rules! timer_channels {
/// Enable the input capture to begin capturing timer values.
#[allow(dead_code)]
pub fn enable(&mut self) {
// Read the latest input capture to clear any pending data in the register.
let _ = self.latest_capture();
// Note(unsafe): This channel owns all access to the specific timer channel.
// Only atomic operations on completed on the timer registers.
let regs = unsafe { &*<$TY>::ptr() };
@ -330,6 +341,18 @@ macro_rules! timer_channels {
let regs = unsafe { &*<$TY>::ptr() };
regs.sr.read().[< cc $index of >]().bit_is_set()
}
/// Configure the input capture input pre-filter.
///
/// # Args
/// * `filter` - The desired input filter stage configuration. Defaults to disabled.
#[allow(dead_code)]
pub fn configure_filter(&mut self, filter: super::InputFilter) {
// Note(unsafe): This channel owns all access to the specific timer channel.
// Only atomic operations on completed on the timer registers.
let regs = unsafe { &*<$TY>::ptr() };
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
}
}
// Note(unsafe): This manually implements DMA support for input-capture channels. This

View File

@ -1,16 +1,7 @@
#![no_std]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#[macro_use]
extern crate log;
pub mod hardware;
// 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/256 MHz = 390.625 KHz
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 8;
pub const ADC_SAMPLE_TICKS: u16 = 1 << 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;