diff --git a/.github/bors.toml b/.github/bors.toml index 4622d5d..722246d 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -1,3 +1,7 @@ block_labels = [ "S-blocked" ] delete_merged_branches = true -status = ["ci"] +status = [ + "style", + "test (stable)", + "compile (stable)", +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fa0e99..f4b66b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5be1311 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 7d0cb25..34941fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 23aab3a..6942942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/cargosha256-dual-iir.nix b/cargosha256-dual-iir.nix index 8775167..d58968f 100644 --- a/cargosha256-dual-iir.nix +++ b/cargosha256-dual-iir.nix @@ -1 +1 @@ -"0ysy8fg6kbblhmjyavq6pg77n21fcygwc0hvidmg2yywkhgdi348" +"138kpxzxs73zhmd4xi5kw3fddb05gac4mpngizm01831n1ycyhl0" diff --git a/dsp/Cargo.toml b/dsp/Cargo.toml index d0d46d8..9c6b411 100644 --- a/dsp/Cargo.toml +++ b/dsp/Cargo.toml @@ -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" diff --git a/dsp/src/complex.rs b/dsp/src/complex.rs index 256cde2..6353f9a 100644 --- a/dsp/src/complex.rs +++ b/dsp/src/complex.rs @@ -1,8 +1,19 @@ +use core::ops::Mul; + use super::{atan2, cossin}; #[derive(Copy, Clone, Default, PartialEq, Debug)] pub struct Complex(pub T, pub T); +impl Complex { + pub fn map(&self, func: F) -> Self + where + F: Fn(T) -> T, + { + Complex(func(self.0), func(self.1)) + } +} + impl Complex { /// Return a Complex on the unit circle given an angle. /// @@ -21,19 +32,43 @@ impl Complex { /// 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 { /// /// ``` /// 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 { + 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 for Complex { + 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 for Complex { + 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, + ) + } +} diff --git a/dsp/src/iir_int.rs b/dsp/src/iir_int.rs index daff43c..f0ce157 100644 --- a/dsp/src/iir_int.rs +++ b/dsp/src/iir_int.rs @@ -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); } } diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index f42e7c7..31ff680 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -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; diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs index ea80836..6ebf4cb 100644 --- a/dsp/src/lockin.rs +++ b/dsp/src/lockin.rs @@ -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; 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 { + /// The lowpass has a gain of `1 << k`. + pub fn update(&mut self, sample: i16, phase: i32, k: u8) -> Complex { // 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), ) } } diff --git a/dsp/src/lowpass.rs b/dsp/src/lowpass.rs new file mode 100644 index 0000000..91fae2f --- /dev/null +++ b/dsp/src/lowpass.rs @@ -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> { + // IIR state storage + y: GenericArray, +} + +impl> Lowpass { + /// 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 + } +} diff --git a/dsp/src/rpll.rs b/dsp/src/rpll.rs index d4bd86f..e312393 100644 --- a/dsp/src/rpll.rs +++ b/dsp/src/rpll.rs @@ -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, Vec) { - 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::::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]); + } } diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index a8dfa39..841c62d 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -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, + mqtt_interface: MqttInterface, // 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(); diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index c32fa81..3eafcd8 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -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, - - // 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, 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::() as u16 ^ 0x8000; - dac_samples[1][i] = - output[1].to_int_unchecked::() 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); diff --git a/src/bin/lockin-internal.rs b/src/bin/lockin-internal.rs index e14a4da..c4d5bd6 100644 --- a/src/bin/lockin-internal.rs +++ b/src/bin/lockin-internal.rs @@ -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; } } diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 1cb6c17..3f2fa5d 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -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}, diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index d7def67..fac712a 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -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( diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 5ca65fb..d41ae8c 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -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, diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 3de7c15..ddc0614 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -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; diff --git a/src/hardware/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs index 6bd8629..baddeba 100644 --- a/src/hardware/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -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, diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index e1e6731..b224541 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -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}; diff --git a/src/hardware/pounder/mod.rs b/src/hardware/pounder/mod.rs index 3a85937..0497a37 100644 --- a/src/hardware/pounder/mod.rs +++ b/src/hardware/pounder/mod.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -mod attenuators; +pub mod attenuators; mod dds_output; pub mod hrtimer; mod rf_power; diff --git a/src/hardware/pounder/timestamp.rs b/src/hardware/pounder/timestamp.rs index e97bc2c..0c06192 100644 --- a/src/hardware/pounder/timestamp.rs +++ b/src/hardware/pounder/timestamp.rs @@ -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. diff --git a/src/hardware/timers.rs b/src/hardware/timers.rs index b686220..78d27b6 100644 --- a/src/hardware/timers.rs +++ b/src/hardware/timers.rs @@ -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 diff --git a/src/lib.rs b/src/lib.rs index 26c60eb..0c9bf2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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;