diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a0ef72b7..28ea2d7c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,9 +62,11 @@ jobs: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable # https://github.com/rust-lang/cargo/issues/13051 - - run: cargo run --bin dual-iir --target x86_64-unknown-linux-gnu - - run: cargo run --bin lockin --target x86_64-unknown-linux-gnu - - run: cargo run --bin dds --target x86_64-unknown-linux-gnu + - run: cargo run --bin dual-iir --target host-tuple + - run: cargo run --bin lockin --target host-tuple + - run: cargo run --bin dds --target host-tuple + - run: cargo run --bin mpll --target host-tuple + # - run: cargo run --bin fls --target host-tuple doc: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ab9b8f07..b92ca51e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ jobs: target/*/release/lockin target/*/release/fls target/*/release/dds + target/*/release/mpll - id: create_release uses: actions/create-release@v1 env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 513bd67fb..1cf6576c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Support for "cooked" PID biquad and standard biquad filter prototypes from `idsp` * Defaulting to `s` optimization for debug and release * `fls`: application for fiber length stabilization and phase/frequency measurement/control +* `mpll`: dispersive PLL ### Changed diff --git a/Cargo.lock b/Cargo.lock index 5dcf2b34b..51e5f5c4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ dependencies = [ "arbitrary-int", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -124,7 +124,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -141,9 +141,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -196,7 +196,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -211,8 +211,18 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -226,7 +236,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.108", + "syn 2.0.113", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.113", ] [[package]] @@ -235,9 +258,20 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", + "quote", + "syn 2.0.113", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -248,9 +282,25 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] +[[package]] +name = "dsp-fixedpoint" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e6691b4edb40c0a1bd948fa524482e3f36f690d782fcabdc39b5d52f6dcf8" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "dsp-process" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f999350286aca82b7fb79f05318f2abe0967b1f068d7a24c573c02369d1153f5" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -382,9 +432,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fnv" @@ -403,9 +453,9 @@ dependencies = [ [[package]] name = "fugit" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" dependencies = [ "gcd", ] @@ -454,9 +504,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" dependencies = [ "bitflags 2.10.0", "libc", @@ -494,9 +544,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heapless" @@ -523,6 +573,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32 0.3.1", + "serde_core", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -531,9 +592,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -544,9 +605,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -557,11 +618,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -572,42 +632,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -644,10 +700,13 @@ dependencies = [ [[package]] name = "idsp" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d1e6135ee1f9e22ef8c3ade3f851c8af40f4bb472c5c7739fd7254035fff2e" +checksum = "f514ce6181d9bf2385f0a0ba7df1d63ea4c561ac7b86067345a9e73fba5c15f7" dependencies = [ + "bytemuck", + "dsp-fixedpoint", + "dsp-process", "miniconf", "num-complex 0.4.6", "num-traits", @@ -658,9 +717,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -668,9 +727,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -684,15 +743,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -708,9 +767,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -720,9 +779,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lm75" @@ -744,9 +803,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "managed" @@ -785,10 +844,10 @@ dependencies = [ [[package]] name = "miniconf" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111e5168eaece53a023515c386ebe4581e382a21cb91f1134c3a2efcddb9bf71" +source = "git+https://github.com/quartiq/miniconf.git#6bb2b28188e5803677c75edbcc830e32be288334" dependencies = [ "heapless 0.8.0", + "heapless 0.9.2", "itoa", "miniconf_derive", "postcard", @@ -803,13 +862,12 @@ dependencies = [ [[package]] name = "miniconf_derive" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9826a0808d011e3217d93fd84ac670d75782d1f5962438b53a3f580f4dc14016" +source = "git+https://github.com/quartiq/miniconf.git#6bb2b28188e5803677c75edbcc830e32be288334" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -966,7 +1024,7 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1038,9 +1096,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "postcard" @@ -1055,9 +1113,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1081,23 +1139,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] name = "proc-macro2" -version = "1.0.102" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1140,7 +1198,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1183,7 +1241,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1259,15 +1317,15 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -1334,12 +1392,13 @@ dependencies = [ [[package]] name = "serde-reflection" -version = "0.5.0" -source = "git+https://github.com/quartiq/serde-reflection.git?branch=pub-ser-de#2910c9c3422ee7b7fe2bffe7e1f929778b217607" +version = "0.5.1" +source = "git+https://github.com/zefchain/serde-reflection.git#b19be2ccd00a4956c4283b82661b9a0995b11518" dependencies = [ "erased-discriminant", "once_cell", "serde", + "serde_json", "thiserror 1.0.69", "typeid", ] @@ -1361,28 +1420,28 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "indexmap", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "serde_core", "serde_with_macros", @@ -1390,14 +1449,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1524,6 +1583,8 @@ dependencies = [ "bytemuck", "cortex-m", "cortex-m-rt", + "dsp-fixedpoint", + "dsp-process", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-bus", @@ -1650,7 +1711,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1666,9 +1727,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.108" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", @@ -1695,7 +1756,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1735,7 +1796,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] @@ -1746,14 +1807,14 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -1773,15 +1834,15 @@ checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -1883,9 +1944,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yafnv" @@ -1898,11 +1959,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1910,13 +1970,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", "synstructure", ] @@ -1937,15 +1997,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", "synstructure", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -1954,9 +2014,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -1965,11 +2025,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.113", ] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/Cargo.toml b/Cargo.toml index b0c7f57a2..e6eeae7c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,20 @@ members = [ "stream", "platform", ] +[workspace.dependencies] +arbitrary-int = { version = "1.3.0", features = ["serde", "hint"] } +thiserror = { version = "2.0.11", default-features = false } +idsp = "0.20.0" +dsp-process = "0.1.0" +dsp-fixedpoint = "0.1.0" +# Keep this synced with the miniconf version in py/setup.py +miniconf = { version = "0.20", features = [ + "json-core", + "derive", + "postcard", + "heapless", +] } +heapless = { version = "0.8", features = ["serde"] } [dependencies] panic-persist = { version = "0.3", features = ["utf8", "custom-panic-handler"] } @@ -50,11 +64,13 @@ log = { version = "0.4", features = [ "release_max_level_info", ] } serde = { version = "1.0", features = ["derive"], default-features = false } -heapless = { version = "0.8", features = ["serde"] } +heapless.workspace = true rtic-monotonics = { version = "2.0", features = ["cortex-m-systick"] } num_enum = { version = "0.7.3", default-features = false } paste = "1" -idsp = "0.19.0" +idsp.workspace = true +dsp-process.workspace = true +dsp-fixedpoint.workspace = true ad9959 = { path = "ad9959", version = "0.3.0" } serial_settings = { version = "0.2", path = "serial_settings" } mcp230xx = "1.0" @@ -66,13 +82,12 @@ shared-bus = { version = "0.3", features = ["cortex-m"] } lm75 = "0.2" usb-device = "0.3.2" usbd-serial = "0.2" -# Keep this synced with the miniconf version in py/setup.py -miniconf = { version = "0.20", features = ["json-core", "derive", "postcard"] } +miniconf.workspace = true miniconf_mqtt = { version = "0.20" } tca9539 = "0.2" bitbybit = "1.3.3" -arbitrary-int = { version = "1.3.0", features = ["serde", "hint"] } -thiserror = { version = "2.0.11", default-features = false } +arbitrary-int.workspace = true +thiserror.workspace = true embedded-hal-compat = "0.13.0" embedded-hal-02 = { package = "embedded-hal", version = "0.2.7", features = [ "unproven", @@ -155,4 +170,5 @@ debug = true lto = true [patch.crates-io] -serde-reflection = { git = "https://github.com/quartiq/serde-reflection.git", branch = "pub-ser-de" } +miniconf = { git = "https://github.com/quartiq/miniconf.git" } +serde-reflection = { git = "https://github.com/zefchain/serde-reflection.git" } diff --git a/ad9912/Cargo.toml b/ad9912/Cargo.toml index 01e91c947..36485ed09 100644 --- a/ad9912/Cargo.toml +++ b/ad9912/Cargo.toml @@ -10,8 +10,8 @@ categories = ["embedded", "no-std"] keywords = ["spi"] [dependencies] -arbitrary-int = "1.3.0" -thiserror = { version = "2.0.11", default-features = false } +arbitrary-int.workspace = true +thiserror.workspace = true num-traits = { version = "0.2.19", default-features = false } embedded-hal = "1.0" bitbybit = "1.3.3" diff --git a/ad9959/Cargo.toml b/ad9959/Cargo.toml index 85bac4259..aad80caa7 100644 --- a/ad9959/Cargo.toml +++ b/ad9959/Cargo.toml @@ -11,4 +11,4 @@ description = "AD9959 4-channel DDS SPI driver" embedded-hal = {version = "0.2.7", features = ["unproven"]} bytemuck = "1.21.0" bitbybit = "1.3.3" -arbitrary-int = { version = "1.3.0", features = ["serde"] } +arbitrary-int.workspace = true diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 1ec2e92ff..2d45ed4d2 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,3 +7,4 @@ - [Application: Lockin](./firmware/lockin/index.html) - [Application: Urukul DDS](./firmware/dds/index.html) - [Application: Fiber Length Stabilization](./firmware/fls/index.html) +- [Application: Dispersive PLL](./firmware/mpll/index.html) diff --git a/book/src/overview.md b/book/src/overview.md index 343d1fd6c..d56853132 100644 --- a/book/src/overview.md +++ b/book/src/overview.md @@ -38,6 +38,7 @@ information. | [`lockin`](firmware/lockin/index.html) | Lockin amplifier support various various reference sources | | [`dds`](firmware/dds/index.html) | Urukul-AD9912 control over MQTT | | [`fls`](firmware/fls/index.html) | Fiber length stabilizer/Phase-Frequency Metrology and control | +| [`mpll`](firmware/mpll/index.html) | Dispersive PLL | ## Library Documentation diff --git a/encoded_pin/Cargo.toml b/encoded_pin/Cargo.toml index c7d895557..6002e9032 100644 --- a/encoded_pin/Cargo.toml +++ b/encoded_pin/Cargo.toml @@ -12,5 +12,5 @@ categories = ["embedded", "no-std"] keywords = ["spi"] [dependencies] -arbitrary-int = { version = "1.3.0", features = ["serde"] } +arbitrary-int.workspace = true embedded-hal = { version = "1.0" } diff --git a/platform/Cargo.toml b/platform/Cargo.toml index 9b35e8dd5..9e2e1bbb2 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -10,9 +10,9 @@ categories = ["embedded", "no-std"] keywords = [] [dependencies] -miniconf = { version = "0.20", features = ["derive", "heapless", "postcard"] } +miniconf.workspace = true serde = { version = "1.0", features = ["derive"], default-features = false } -heapless = { version = "0.8", features = ["serde"] } +heapless.workspace = true log = { version = "0.4" } cortex-m = { version = "0.7.7" } sequential-storage = "5" diff --git a/serial_settings/Cargo.toml b/serial_settings/Cargo.toml index 70ace07be..90d282d60 100644 --- a/serial_settings/Cargo.toml +++ b/serial_settings/Cargo.toml @@ -17,9 +17,9 @@ keywords = ["serial", "settings", "cli", "management", "async"] maintenance = { status = "actively-developed" } [dependencies] -miniconf = { version = "0.20", features = ["json-core", "postcard"] } +miniconf.workspace = true menu = { version = "0.6.1", features = ["echo"] } -heapless = "0.8" +heapless.workspace = true embedded-io = "0.6" yafnv = "3.0" postcard = { version = "1", default-features = false } diff --git a/signal_generator/Cargo.toml b/signal_generator/Cargo.toml index 57ff9043e..a3073aaba 100644 --- a/signal_generator/Cargo.toml +++ b/signal_generator/Cargo.toml @@ -14,7 +14,7 @@ keywords = [] [dependencies] rand_xorshift = "0.4.0" rand_core = "0.9.0" -thiserror = { version = "2.0.11", default-features = false } -idsp = "0.19.0" -miniconf = { version = "0.20", features = ["derive"] } serde = { version = "1.0", features = ["derive"], default-features = false } +thiserror.workspace = true +idsp.workspace = true +miniconf.workspace = true diff --git a/signal_generator/src/lib.rs b/signal_generator/src/lib.rs index c876048b4..00746f36c 100644 --- a/signal_generator/src/lib.rs +++ b/signal_generator/src/lib.rs @@ -162,7 +162,9 @@ impl Iterator for Source { #[inline] fn next(&mut self) -> Option { let (s, a) = match self { - Self::SweptSine { sweep, amp } => (sweep.next().map(|c| c.im), amp), + Self::SweptSine { sweep, amp } => { + (sweep.next().map(|c| c.im()), amp) + } Self::Periodic { accu, signal, amp } => { (accu.next().map(|p| signal.map(p)), amp) } diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 64ffc19b1..82371f911 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -30,6 +30,7 @@ use miniconf::Tree; +use dsp_process::SplitProcess; use idsp::iir; use platform::{AppSettings, NetSettings}; @@ -85,17 +86,17 @@ pub struct BiquadRepr { /// Biquad parameters #[tree(rename="typ", typ="&str", with=miniconf::str_leaf, defer=self.repr)] _typ: (), - repr: iir::BiquadRepr, + repr: iir::repr::BiquadRepr, } impl Default for BiquadRepr { fn default() -> Self { - let mut i = iir::Biquad::IDENTITY; - i.set_min(-i16::MAX as _); - i.set_max(i16::MAX as _); + let mut i = iir::BiquadClamp::from(iir::Biquad::IDENTITY); + i.min = -i16::MAX as _; + i.max = i16::MAX as _; Self { _typ: (), - repr: iir::BiquadRepr::Raw(i), + repr: iir::repr::BiquadRepr::Raw(i), } } } @@ -147,11 +148,7 @@ impl Channel { state: Default::default(), run: self.run, biquad: self.biquad.each_ref().map(|biquad| { - biquad.repr.build::( - SAMPLE_PERIOD, - 1.0, - DacCode::LSB_PER_VOLT, - ) + biquad.repr.build(SAMPLE_PERIOD, 1.0, DacCode::LSB_PER_VOLT) }), }) } @@ -189,8 +186,8 @@ impl Default for DualIir { #[derive(Clone, Debug)] pub struct Active { run: Run, - biquad: [iir::Biquad; IIR_CASCADE_LENGTH], - state: [[f32; 4]; IIR_CASCADE_LENGTH], + biquad: [iir::BiquadClamp; IIR_CASCADE_LENGTH], + state: [iir::DirectForm1; IIR_CASCADE_LENGTH], source: Source, } @@ -386,12 +383,11 @@ mod app { .iter() .zip(active.state.iter_mut()) .fold(x, |y, (ch, state)| { - let filter = if active.run.run(di) { - ch + if active.run.run(di) { + ch.process(state, y) } else { - &iir::Biquad::HOLD - }; - filter.update(state, y) + iir::Biquad::::HOLD.process(state, y) + } }); // Note(unsafe): The filter limits must ensure that the value is in range. @@ -477,11 +473,7 @@ mod app { ( ch.run, ch.biquad.each_ref().map(|b| { - b.repr.build::( - SAMPLE_PERIOD, - 1.0, - DacCode::LSB_PER_VOLT, - ) + b.repr.build(SAMPLE_PERIOD, 1.0, DacCode::LSB_PER_VOLT) }), ) }); diff --git a/src/bin/fls.rs b/src/bin/fls.rs index 8867cd229..769b9569e 100644 --- a/src/bin/fls.rs +++ b/src/bin/fls.rs @@ -77,12 +77,21 @@ //! See [stream]. To view and analyze noise spectra the graphical application //! [`stabilizer-stream`](https://github.com/quartiq/stabilizer-stream) can be used. +use core::num::Wrapping as W; + use ad9959::Acr; use arbitrary_int::{u14, u24}; +use dsp_fixedpoint::{Q32, W32}; +use dsp_process::{Process, SplitProcess}; use idsp::{ - Accu, Complex, ComplexExt, Filter, Lockin, Lowpass, PLL, Unwrapper, iir, + Clamp, Complex, Lockin, Lowpass, LowpassState, PLL, Unwrapper, + iir::{ + Biquad, BiquadClamp, DirectForm1, DirectForm1Dither, pid::Pid, + repr::BiquadRepr, + }, }; use miniconf::Tree; +use num_traits::AsPrimitive; use platform::NetSettings; use serde::{Deserialize, Serialize}; use stabilizer::{ @@ -140,12 +149,14 @@ const PHASE_SCALE_SHIFT: u32 = 12; const F_DEMOD: u32 = 0x5200_0000; #[derive(Clone, Debug, Tree)] -pub struct BiquadRepr +pub struct BiquadReprTree where - T: idsp::Coefficient - + num_traits::AsPrimitive - + num_traits::AsPrimitive, - f32: num_traits::AsPrimitive, + T: 'static + Clamp + Copy, + Y: 'static + Copy, + f32: AsPrimitive + AsPrimitive, + BiquadClamp: Default, + BiquadRepr: Default, + Pid: Default + Into>, { // Order matters /// Biquad representation type @@ -153,13 +164,13 @@ where _typ: (), /// Biquad parameters /// Biquad representation subtree access - repr: iir::BiquadRepr, + repr: BiquadRepr, /// Update trigger. TODO: Needs explicit trigger for serial-settings #[tree(rename="update", with=biquad_update, defer=*self)] _update: (), /// Built raw IIR #[tree(skip)] - iir: iir::Biquad, + iir: BiquadClamp, #[tree(skip)] period: f32, #[tree(skip)] @@ -169,46 +180,51 @@ where } mod biquad_update { - use super::BiquadRepr; + use super::BiquadReprTree; + use idsp::Clamp; + use idsp::iir::{BiquadClamp, pid::Pid, repr::BiquadRepr}; use miniconf::{Keys, SerdeError, leaf}; pub use miniconf::{ deny::{mut_any_by_key, ref_any_by_key}, leaf::SCHEMA, }; + use num_traits::AsPrimitive; use serde::{Deserialize, Deserializer, Serializer}; - pub fn serialize_by_key( - _value: &BiquadRepr, + pub fn serialize_by_key( + _value: &BiquadReprTree, keys: impl Keys, ser: S, ) -> Result> where S: Serializer, - T: idsp::Coefficient - + num_traits::AsPrimitive - + num_traits::AsPrimitive, - f32: num_traits::AsPrimitive, + T: 'static + Clamp + Copy, + Y: 'static + Copy, + f32: AsPrimitive + AsPrimitive, + BiquadRepr: Default, + BiquadClamp: Default, + Pid: Into>, { leaf::serialize_by_key(&(), keys, ser) } - pub fn deserialize_by_key<'de, D, T>( - value: &mut BiquadRepr, + pub fn deserialize_by_key<'de, D, T, Y>( + value: &mut BiquadReprTree, keys: impl Keys, de: D, ) -> Result<(), SerdeError> where D: Deserializer<'de>, - T: idsp::Coefficient - + num_traits::AsPrimitive - + num_traits::AsPrimitive, - f32: num_traits::AsPrimitive, + T: 'static + Clamp + Copy, + Y: 'static + Copy, + f32: AsPrimitive + AsPrimitive, + BiquadRepr: Default, + BiquadClamp: Default, + Pid: Into>, { leaf::deserialize_by_key(&mut (), keys, de)?; value.iir = - value - .repr - .build::(value.period, value.b_scale, value.y_scale); + value.repr.build(value.period, value.b_scale, value.y_scale); Ok(()) } @@ -225,19 +241,21 @@ mod biquad_update { } } -impl Default for BiquadRepr +impl Default for BiquadReprTree where - T: idsp::Coefficient - + num_traits::AsPrimitive - + num_traits::AsPrimitive, - f32: num_traits::AsPrimitive, + T: 'static + Clamp + Copy, + Y: 'static + Copy, + f32: AsPrimitive + AsPrimitive, + BiquadClamp: Default, + Pid: Into>, + BiquadRepr: Default, { fn default() -> Self { Self { _typ: (), - repr: iir::BiquadRepr::Raw(iir::Biquad::IDENTITY), + repr: BiquadRepr::Raw(Biquad::IDENTITY.into()), _update: (), - iir: iir::Biquad::IDENTITY, + iir: Biquad::IDENTITY.into(), period: 1.0, b_scale: 1.0, y_scale: 1.0, @@ -293,18 +311,16 @@ mod validate_att { ) -> Result<(), SerdeError> { let mut att = *value; leaf::deserialize_by_key(&mut att, keys, de)?; - if !stabilizer::convert::att_is_valid(att) { - Err(ValueError::Access("Attenuation out of range (0..=31.5 dB)") - .into()) - } else { + if stabilizer::convert::att_is_valid(att) { *value = att; Ok(()) + } else { + Err(ValueError::Access("Attenuation out of range (0..=31.5 dB)") + .into()) } } } -type LockinLowpass = Lowpass<2>; - #[derive(Clone, Debug, Tree)] struct ChannelSettings { /// Input (demodulation) DDS settings @@ -337,8 +353,8 @@ struct ChannelSettings { /// /// # Default /// `lockin_k = [0x200_0000, -0x2000_0000]` - #[tree(with=miniconf::leaf)] - lockin_k: ::Config, + #[tree(skip)] // TODO + lockin_k: Lockin>, /// Minimum demodulated signal power to enable feedback. /// Note that this is RMS and that the signal peak must not clip. /// @@ -371,7 +387,7 @@ struct ChannelSettings { /// /// # Default /// A proportional gain=-1 filter. - iir: BiquadRepr, + iir: BiquadReprTree, i32>, /// Phase offset feedback gain. /// Phase feedback is a proportional bypass of the unwrapper, the IIR /// (including its input and output scaling) and the frequency feedback path. @@ -389,7 +405,7 @@ struct ChannelSettings { /// /// # Default /// No feedback - iir_amp: BiquadRepr, + iir_amp: BiquadReprTree, } const DDS_LSB_PER_HZ: f32 = (1i64 << 32) as f32 @@ -397,14 +413,14 @@ const DDS_LSB_PER_HZ: f32 = (1i64 << 32) as f32 impl Default for ChannelSettings { fn default() -> Self { - let mut iir_prop = iir::Biquad::IDENTITY; - iir_prop.ba_mut()[0] *= -1; - iir_prop.set_min(-0x4_0000); - iir_prop.set_max(0x4_0000); - let mut iir_amp = iir::Biquad::default(); - iir_amp.set_u(0x3ff as _); - iir_amp.set_min(0.0); - iir_amp.set_max(0x3ff as _); + let mut iir_prop = BiquadClamp::from(Biquad::IDENTITY); + iir_prop.coeff.ba[0] *= -1; + iir_prop.min = -0x4_0000; + iir_prop.max = 0x4_0000; + let mut iir_amp = BiquadClamp::default(); + iir_amp.u = 0x3ff as _; + iir_amp.min = 0.0; + iir_amp.max = 0x3ff as _; let mut s = Self { input: DdsSettings { freq: F_DEMOD, @@ -416,14 +432,14 @@ impl Default for ChannelSettings { att: 6.0, phase: u14::new(0), }, - lockin_k: [-(i32::MIN >> 6), i32::MIN >> 2], + lockin_k: Lockin(Lowpass([-(i32::MIN >> 6), i32::MIN >> 2])), amp: u24::new(0), min_power: -24, clear: true, phase_scale: [[1, 16], [0, 0]], - iir: BiquadRepr { - repr: iir::BiquadRepr::Raw(iir_prop.clone()), - iir: iir_prop, + iir: BiquadReprTree { + repr: BiquadRepr::Raw(iir_prop.clone()), + iir: iir_prop.clone(), period: stabilizer::design_parameters::TIMER_PERIOD * (SAMPLE_TICKS * BATCH_SIZE as u32) as f32, y_scale: DDS_LSB_PER_HZ, @@ -431,12 +447,12 @@ impl Default for ChannelSettings { }, pow_gain: 0, hold_en: false, - iir_amp: BiquadRepr { - repr: iir::BiquadRepr::Raw(iir_amp.clone()), + iir_amp: BiquadReprTree { + repr: BiquadRepr::Raw(iir_amp.clone()), iir: iir_amp.clone(), period: 10e-3, - b_scale: iir_amp.max(), - y_scale: iir_amp.max(), + b_scale: iir_amp.max, + y_scale: iir_amp.max, ..Default::default() }, }; @@ -523,7 +539,8 @@ pub struct Fls { /// /// # Default /// `/pll_k = 0x4_0000` corresponds to to a time constant of about 0.4 s. - pll_k: i32, + #[tree(with=miniconf::leaf)] + pll_k: W32<32>, /// Telemetry output period in seconds /// /// # Default @@ -543,7 +560,7 @@ impl Default for Fls { ch: Default::default(), ext_clk: false, lockin_freq: 0x40, - pll_k: 0x4_0000, + pll_k: W32::new(W(0x4_0000)), telemetry_period: 10, stream: Default::default(), } @@ -658,14 +675,14 @@ pub struct CookedTelemetry { #[derive(Clone, Default)] pub struct ChannelState { - lockin: Lockin, - x0: i32, - t0: i32, - t: i64, - y: i64, + lockin: [LowpassState<2>; 2], + x0: W, + t0: W, + t: W, + y: W, unwrapper: Unwrapper, - iir: [i32; 5], - iir_amp: [f32; 4], + iir: DirectForm1Dither, + iir_amp: DirectForm1, hold: bool, } @@ -690,6 +707,7 @@ mod app { use arbitrary_int::u10; use core::sync::atomic::{Ordering, fence}; use fugit::ExtU32 as _; + use num_traits::ConstZero; use rtic_monotonics::Monotonic; use stabilizer::hardware::{ @@ -852,7 +870,7 @@ mod app { let timestamp = timestamper .latest_timestamp() .unwrap_or(None) - .map(|t| ((t as u32) << 16) as i32); + .map(|t| W(((t as u32) << 16) as i32)); ( c.shared.state, @@ -863,20 +881,19 @@ mod app { .lock(|state, settings, dds_output, telemetry| { // Reconstruct frequency and phase using a lowpass that is aware of phase and frequency // wraps. - pll.update(timestamp, settings.pll_k); + let mut accu = settings.pll_k.process(pll, timestamp); // TODO: implement clear - stream[0].pll = pll.frequency() as _; - stream[1].pll = pll.phase() as _; + stream[0].pll = accu.step.0 as _; + stream[1].pll = accu.state.0 as _; telemetry.pll_time = - telemetry.pll_time.wrapping_add(pll.frequency() as _); + telemetry.pll_time.wrapping_add(pll.frequency().0 as _); - let mut demod = [Complex::::default(); BATCH_SIZE]; + let mut demod = [Complex::>::default(); BATCH_SIZE]; + accu.state <<= BATCH_SIZE_LOG2 as usize; + accu.state *= W(settings.lockin_freq as i32); + accu.step *= W(settings.lockin_freq as i32); // TODO: fixed lockin_freq, const 5/16 frequency table (80 entries), then rotate each by pll phase - for (d, p) in demod.iter_mut().zip(Accu::new( - (pll.phase() << BATCH_SIZE_LOG2) - .wrapping_mul(settings.lockin_freq as _), - pll.frequency().wrapping_mul(settings.lockin_freq as _), - )) { + for (d, p) in demod.iter_mut().zip(accu) { *d = Complex::from_angle(p); } @@ -906,14 +923,13 @@ mod app { // Demodulate the ADC sample `a0` with the sample's phase `p` and // filter it with the lowpass. // zero(s) at fs/2 (Nyquist) by lowpass - let y = state.lockin.update_iq( + let y = settings.lockin_k.process( + &mut state.lockin, // 3 bit headroom for coeff sum minus one bit gain for filter - (*a as i16 as i32) << 14, - *p, - &settings.lockin_k, + ((*a as i16 as i32) << 14, *p), ); // Convert quadrature demodulated output to DAC data for monitoring - *d = DacCode::from((y.im >> 13) as i16).0; + *d = DacCode::from((y.im() >> 13) as i16).0; y }) // Add more zeros at fs/2, fs/4, and fs/8 by rectangular window. @@ -927,8 +943,8 @@ mod app { let di = [digital_inputs.0.is_high(), digital_inputs.1.is_high()]; // TODO: pll.frequency()? - let time = pll.phase() & (-1 << PHASE_SCALE_SHIFT); - let dtime = time.wrapping_sub(state[0].t0) >> PHASE_SCALE_SHIFT; + let time = pll.phase() & W(-1 << PHASE_SCALE_SHIFT); + let dtime = (time - state[0].t0) >> PHASE_SCALE_SHIFT as usize; state[0].t0 = time; state[1].t0 = time; @@ -952,8 +968,8 @@ mod app { if settings.clear { state.unwrapper = Unwrapper::default(); - state.t = 0; - state.y = 0; + state.t = W(0); + state.y = W(0); settings.clear = false; } @@ -973,42 +989,43 @@ mod app { (0, stream.delta_pow) } else { let phase = stream.demod.arg(); - let dphase = phase.wrapping_sub(state.x0); + let dphase = phase - state.x0; state.x0 = phase; // |dphi| > pi/2 indicates a possible undetected phase slip. // Neither a necessary nor a sufficient condition though. - if dphase.wrapping_add(1 << 30) < 0 { + if dphase + W(1 << 30) < W::ZERO { telemetry.slips = telemetry.slips.wrapping_add(1); } // Scale, offset and unwrap phase - state.t = state.t.wrapping_add( - dtime as i64 * settings.phase_scale[1][0] as i64, - ); - state.y = state.y.wrapping_add( - dphase as i64 * settings.phase_scale[0][0] as i64, - ); - state.unwrapper.update( - ((state.y >> settings.phase_scale[0][1]) as i32) + state.t += + dtime.0 as i64 * settings.phase_scale[1][0] as i64; + state.y += + dphase.0 as i64 * settings.phase_scale[0][0] as i64; + state.unwrapper.process( + ((state.y >> settings.phase_scale[0][1] as usize).0 + as i32) .wrapping_add( - (state.t >> settings.phase_scale[1][1]) + (state.t + >> settings.phase_scale[1][1] as usize) + .0 as i32, ), ); - stream.phase = bytemuck::cast(state.unwrapper.y()); - telemetry.phase = state.unwrapper.y(); - let phase_err = state - .unwrapper - .y() - .clamp(-i32::MAX as _, i32::MAX as _) - as _; + stream.phase = bytemuck::cast(state.unwrapper.y); + telemetry.phase = state.unwrapper.y; + let phase_err = Clamp::clamp( + state.unwrapper.y, + -i32::MAX as _, + i32::MAX as _, + ) as _; stats.update(phase_err); // TODO; unchecked_shr let delta_ftw = - settings.iir.iir.update(&mut state.iir, phase_err); + settings.iir.iir.process(&mut state.iir, phase_err); let delta_pow = ((phase_err >> 16) .wrapping_mul(settings.pow_gain as _) >> 16) as _; @@ -1034,9 +1051,11 @@ mod app { )), Some( Acr::DEFAULT - .with_asf(u10::new( - telemetry.mod_amp.clamp(0, 0x3ff), - )) + .with_asf(u10::new(Clamp::clamp( + telemetry.mod_amp, + 0, + 0x3ff, + ))) .with_multiplier(true), ), ); @@ -1151,10 +1170,10 @@ mod app { .zip(y.iter_mut()) { *y = if c.hold { - iir::Biquad::HOLD.update(&mut c.iir_amp, *x); + Biquad::::HOLD.process(&mut c.iir_amp, *x); 0 } else { - s.iir_amp.iir.update(&mut c.iir_amp, *x) as u16 + s.iir_amp.iir.process(&mut c.iir_amp, *x) as u16 }; } }); diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 8894ec19a..d053b720e 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -30,11 +30,13 @@ use core::{ iter, mem::MaybeUninit, + num::Wrapping, sync::atomic::{Ordering, fence}, }; +use dsp_process::SplitProcess; use fugit::ExtU32; -use idsp::{Accu, Complex, ComplexExt, Filter, Lowpass, RPLL, Repeat}; +use idsp::{Accu, Complex, Lowpass, RPLL, RPLLConfig}; use miniconf::{Leaf, Tree}; use rtic_monotonics::Monotonic; use serde::{Deserialize, Serialize}; @@ -122,22 +124,25 @@ pub struct Lockin { /// Specifis the PLL time constant. /// /// The PLL time constant exponent (1-31). - pll_tc: [u32; 2], + #[tree(with=miniconf::leaf)] + pll_tc: RPLLConfig, /// Specifies the lockin lowpass gains. - #[tree(with=miniconf::leaf)] - lockin_k: as Filter>::Config, + #[tree(skip)] // TODO + lockin_k: idsp::Lockin>, /// Specifies which harmonic to use for the lockin. /// /// Harmonic index of the LO. -1 to _de_modulate the fundamental (complex conjugate) - lockin_harmonic: i32, + #[tree(with=miniconf::leaf)] + lockin_harmonic: Wrapping, /// Specifies the LO phase offset. /// /// Demodulation LO phase offset. Units are in terms of i32, where [i32::MIN] is equivalent to /// -pi and [i32::MAX] is equivalent to +pi. - lockin_phase: i32, + #[tree(with=miniconf::leaf)] + lockin_phase: Wrapping, /// Specifies DAC output mode. output_conf: [Leaf; 2], @@ -157,11 +162,15 @@ impl Default for Lockin { lockin_mode: LockinMode::External, - pll_tc: [21, 21], // frequency and phase settling time (log2 counter cycles) + pll_tc: RPLLConfig { + dt2: (SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2) as _, + shift_frequency: 21, + shift_phase: 21, + }, // frequency and phase settling time (log2 counter cycles) - lockin_k: [0x8_0000, -0x400_0000], // lockin lowpass gains - lockin_harmonic: -1, // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) - lockin_phase: 0, // Demodulation LO phase offset + lockin_k: idsp::Lockin(Lowpass([0x8_0000, -0x400_0000])), // lockin lowpass gains + lockin_harmonic: Wrapping(-1), // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) + lockin_phase: Wrapping(0), // Demodulation LO phase offset output_conf: [Leaf(Conf::InPhase), Leaf(Conf::Quadrature)], // The default telemetry period in seconds. @@ -191,6 +200,8 @@ fn main() { #[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, SDMMC])] mod app { use super::*; + use core::num::Wrapping; + use idsp::LowpassState; use stabilizer::{ hardware::{ self, DigitalInput0, DigitalInput1, Pgia, SerialTerminal, @@ -225,7 +236,7 @@ mod app { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), pll: RPLL, - lockin: idsp::Lockin>>, + lockin: [LowpassState<2>; 2], source: idsp::AccuOsc>, generator: FrameGenerator, cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor, @@ -274,8 +285,8 @@ mod app { timestamper: stabilizer.input_stamper, cpu_temp_sensor: stabilizer.temperature_sensor, - pll: RPLL::new(SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2), - lockin: idsp::Lockin::default(), + pll: RPLL::default(), + lockin: Default::default(), source: idsp::AccuOsc::new(iter::repeat( 1i64 << (64 - BATCH_SIZE_LOG2), )), @@ -340,29 +351,30 @@ mod app { } = c.local; (active_settings, telemetry).lock(|settings, telemetry| { - let (reference_phase, reference_frequency) = - match settings.lockin_mode { - LockinMode::External => { - let timestamp = - timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows. - let (pll_phase, pll_frequency) = pll.update( - timestamp.map(|t| t as i32), - settings.pll_tc[0], - settings.pll_tc[1], - ); - (pll_phase, (pll_frequency >> BATCH_SIZE_LOG2) as i32) - } - LockinMode::Internal => { - // Reference phase and frequency are known. - (1i32 << 30, 1i32 << (32 - BATCH_SIZE_LOG2)) - } - }; + let (reference_phase, reference_frequency) = match settings + .lockin_mode + { + LockinMode::External => { + let timestamp = + timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows. + let accu = settings + .pll_tc + .process(pll, timestamp.map(|t| Wrapping(t as i32))); + (accu.state, (accu.step >> BATCH_SIZE_LOG2 as usize)) + } + LockinMode::Internal => { + // Reference phase and frequency are known. + ( + Wrapping(1i32 << 30), + Wrapping(1i32 << (32 - BATCH_SIZE_LOG2)), + ) + } + }; let sample_frequency = - reference_frequency.wrapping_mul(settings.lockin_harmonic); - let sample_phase = settings.lockin_phase.wrapping_add( - reference_phase.wrapping_mul(settings.lockin_harmonic), - ); + reference_frequency * settings.lockin_harmonic; + let sample_phase = settings.lockin_phase + + reference_phase * settings.lockin_harmonic; (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| { let adc_samples = [adc0, adc1]; @@ -378,7 +390,7 @@ mod app { // Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter) .map(|(&sample, phase)| { let s = (sample as i16 as i32) << 16; - lockin.update(s, phase, &settings.lockin_k) + settings.lockin_k.process(lockin, (s, phase)) }) // Decimate .last() @@ -389,16 +401,18 @@ mod app { for (channel, samples) in dac_samples.iter_mut().enumerate() { for sample in samples.iter_mut() { let value = match *settings.output_conf[channel] { - Conf::Magnitude => output.abs_sqr() as i32 >> 16, - Conf::Phase => output.arg() >> 16, + Conf::Magnitude => { + output.norm_sqr().inner as i32 >> 16 + } + Conf::Phase => output.arg().0 >> 16, Conf::LogPower => output.log2() << 8, Conf::ReferenceFrequency => { - reference_frequency >> 16 + reference_frequency.0 >> 16 } - Conf::InPhase => output.re >> 16, - Conf::Quadrature => output.im >> 16, + Conf::InPhase => output.re() >> 16, + Conf::Quadrature => output.im() >> 16, - Conf::Modulation => source.next().unwrap().re, + Conf::Modulation => source.next().unwrap().re(), }; *sample = DacCode::from(value as i16).0; diff --git a/src/bin/mpll.rs b/src/bin/mpll.rs new file mode 100644 index 000000000..13280e729 --- /dev/null +++ b/src/bin/mpll.rs @@ -0,0 +1,362 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] + +use core::sync::atomic::{Ordering, fence}; + +use fugit::ExtU32; +use miniconf::Tree; +use rtic_monotonics::Monotonic; + +use serde::Serialize; +use stabilizer::{convert::Gain, mpll::*, statistics}; + +use platform::{AppSettings, NetSettings}; + +// 100 MHz timer, 128 divider: period of 1.28 µs, ~781.25 KHz. +const SAMPLE_TICKS_LOG2: u32 = 7; +const SAMPLE_TICKS: u32 = 1 << SAMPLE_TICKS_LOG2; + +#[derive(Clone, Debug, Tree, Default)] +#[tree(meta(doc, typename))] +pub struct Settings { + mpll: App, + net: NetSettings, +} + +impl AppSettings for Settings { + fn new(net: NetSettings) -> Self { + Self { + net, + mpll: App::default(), + } + } + + fn net(&self) -> &NetSettings { + &self.net + } +} + +impl serial_settings::Settings for Settings { + fn reset(&mut self) { + *self = Self { + mpll: App::default(), + net: NetSettings::new(self.net.mac), + } + } +} + +#[derive(Clone, Debug, Tree)] +#[tree(meta(doc, typename))] +pub struct App { + mpll: Mpll, + + /// Specifies the telemetry output period in seconds. + telemetry_period: f32, + + /// Specifies the target for data streaming. + #[tree(with=miniconf::leaf)] + stream: stream::Target, +} + +impl Default for App { + fn default() -> Self { + Self { + telemetry_period: 10., + stream: Default::default(), + mpll: Default::default(), + } + } +} + +/// Channel Telemetry +#[derive(Default, Clone)] +pub struct TelemetryState { + demod: [[statistics::State; 2]; 2], + phase: statistics::State, + frequency: statistics::State, +} + +#[derive(Default, Clone, Serialize)] +pub struct TelemetryCooked { + demod: [[statistics::ScaledStatistics; 2]; 2], + phase: statistics::ScaledStatistics, + frequency: statistics::ScaledStatistics, +} + +impl From for TelemetryCooked { + fn from(t: TelemetryState) -> Self { + Self { + // G10, 1 V fs + demod: t + .demod + .map(|d| d.map(|p| p.get_scaled(1.0 / (1u64 << 31) as f32))), + phase: t.phase.get_scaled(1.0 / (1u64 << 32) as f32), + frequency: t + .frequency + .get_scaled(100e6 / SAMPLE_TICKS as f32 / (1u64 << 32) as f32), + } + } +} + +#[derive(Default, Clone, Serialize)] +pub struct Telemetry { + mpll: TelemetryCooked, + cpu_temp: f32, +} + +#[cfg(not(target_os = "none"))] +fn main() { + use miniconf::{json::to_json_value, json_schema::TreeJsonSchema}; + let s = Settings::default(); + println!( + "{}", + serde_json::to_string_pretty(&to_json_value(&s).unwrap()).unwrap() + ); + let mut schema = TreeJsonSchema::new(Some(&s)).unwrap(); + schema + .root + .insert("title".to_string(), "Stabilizer mpll".into()); + println!("{}", serde_json::to_string_pretty(&schema.root).unwrap()); +} + +#[cfg(target_os = "none")] +#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, dispatchers=[DCMI, JPEG, SDMMC])] +mod app { + use super::*; + use stabilizer::hardware::{ + self, SerialTerminal, SystemTimer, Systick, UsbDevice, + adc::{Adc0Input, Adc1Input}, + dac::{Dac0Output, Dac1Output}, + hal, + net::{NetworkState, NetworkUsers}, + timers::SamplingTimer, + }; + use stream::FrameGenerator; + + #[shared] + struct Shared { + usb: UsbDevice, + network: NetworkUsers, + settings: Settings, + active_settings: Mpll, + telemetry: TelemetryState, + } + + #[local] + struct Local { + usb_terminal: SerialTerminal, + sampling_timer: SamplingTimer, + adcs: (Adc0Input, Adc1Input), + dacs: (Dac0Output, Dac1Output), + state: MpllState, + generator: FrameGenerator, + cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor, + } + + #[init] + fn init(c: init::Context) -> (Shared, Local) { + let clock = SystemTimer::new(|| Systick::now().ticks()); + + // Configure the microcontroller + let (mut carrier, _mezzanine, _eem) = hardware::setup::setup::( + c.core, + c.device, + clock, + BATCH_SIZE, + SAMPLE_TICKS, + ); + + carrier.afes[0].set_gain(Gain::G10); + carrier.afes[1].set_gain(Gain::G10); + + let mut network = NetworkUsers::new( + carrier.network_devices.stack, + carrier.network_devices.phy, + clock, + env!("CARGO_BIN_NAME"), + &carrier.settings.net, + carrier.metadata, + ); + + let generator = network.configure_streaming(stream::Format::Mpll); + + let shared = Shared { + network, + usb: carrier.usb, + telemetry: Default::default(), + active_settings: carrier.settings.mpll.mpll.clone(), + settings: carrier.settings, + }; + + let mut local = Local { + usb_terminal: carrier.usb_serial, + sampling_timer: carrier.sampling_timer, + adcs: carrier.adcs, + dacs: carrier.dacs, + cpu_temp_sensor: carrier.temperature_sensor, + state: Default::default(), + generator, + }; + + // Enable ADC/DAC events + local.adcs.0.start(); + local.adcs.1.start(); + local.dacs.0.start(); + local.dacs.1.start(); + + settings_update::spawn().unwrap(); + telemetry::spawn().unwrap(); + ethernet_link::spawn().unwrap(); + start::spawn().unwrap(); + usb::spawn().unwrap(); + + (shared, local) + } + + #[task(priority = 1, local=[sampling_timer])] + async fn start(c: start::Context) { + Systick::delay(100.millis()).await; + // Start sampling ADCs and DACs. + c.local.sampling_timer.start(); + } + + #[task(binds=DMA1_STR4, shared=[active_settings, telemetry], local=[adcs, dacs, state, generator], priority=3)] + #[unsafe(link_section = ".itcm.process")] + fn process(c: process::Context) { + let process::SharedResources { + active_settings, + telemetry, + .. + } = c.shared; + + let process::LocalResources { + adcs: (adc0, adc1), + dacs: (dac0, dac1), + state, + generator, + .. + } = c.local; + + (active_settings, telemetry).lock(|settings, telemetry| { + (adc0, adc1, dac0, dac1).lock(|adc0, adc1, dac0, dac1| { + // Preserve instruction and data ordering w.r.t. DMA flag access. + fence(Ordering::SeqCst); + let adc: [&[u16; BATCH_SIZE]; 2] = [ + (**adc0).try_into().unwrap(), + (**adc1).try_into().unwrap(), + ]; + let dac: [&mut [u16; BATCH_SIZE]; 2] = + [(*dac0).try_into().unwrap(), (*dac1).try_into().unwrap()]; + + let (stream, frequency) = settings.process(state, adc, dac); + telemetry.phase.update(stream.phase_in); + telemetry.frequency.update(frequency); + + telemetry.demod[0][0].update(stream.demod[0]); + telemetry.demod[0][1].update(stream.demod[1]); + telemetry.demod[1][0].update(stream.demod[2]); + telemetry.demod[1][1].update(stream.demod[3]); + + const N: usize = core::mem::size_of::(); + generator.add(|buf| { + buf[..N].copy_from_slice(bytemuck::cast_slice( + bytemuck::bytes_of(&stream), + )); + N + }); + + // Preserve instruction and data ordering w.r.t. DMA flag access. + fence(Ordering::SeqCst); + }); + }); + } + + #[idle(shared=[settings, network, usb])] + fn idle(mut c: idle::Context) -> ! { + loop { + match (&mut c.shared.network, &mut c.shared.settings) + .lock(|net, settings| net.update(&mut settings.mpll)) + { + NetworkState::SettingsChanged => { + settings_update::spawn().unwrap() + } + NetworkState::Updated => {} + NetworkState::NoChange => { + // We can't sleep if USB is not in suspend. + if c.shared.usb.lock(|usb| { + usb.state() + == usb_device::device::UsbDeviceState::Suspend + }) { + cortex_m::asm::wfi(); + } + } + } + } + } + + #[task(priority = 1, shared=[network, settings, active_settings])] + async fn settings_update(mut c: settings_update::Context) { + c.shared.settings.lock(|settings| { + c.shared + .network + .lock(|net| net.direct_stream(settings.mpll.stream)); + let new = Mpll::new(&settings.mpll.mpll); + c.shared.active_settings.lock(|current| *current = new); + }); + } + + #[task(priority = 1, local=[cpu_temp_sensor], shared=[network, settings, telemetry])] + async fn telemetry(mut c: telemetry::Context) -> ! { + loop { + let tele = Telemetry { + mpll: c.shared.telemetry.lock(|t| core::mem::take(t)).into(), + cpu_temp: c + .local + .cpu_temp_sensor + .get_temperature() + .unwrap_or_default(), + }; + c.shared.network.lock(|net| { + net.telemetry.publish_telemetry("/telemetry", &tele) + }); + + let delay = c.shared.settings.lock(|s| s.mpll.telemetry_period); + Systick::delay(((delay * 1000.0) as u32).millis()).await; + } + } + + #[task(priority = 1, shared=[usb, settings], local=[usb_terminal])] + async fn usb(mut c: usb::Context) -> ! { + loop { + // Handle the USB serial terminal. + c.shared.usb.lock(|usb| { + usb.poll(&mut [c + .local + .usb_terminal + .interface_mut() + .inner_mut()]); + }); + + c.shared.settings.lock(|settings| { + if c.local.usb_terminal.poll(settings).unwrap() { + settings_update::spawn().unwrap() + } + }); + + Systick::delay(10.millis()).await; + } + } + + #[task(priority = 1, shared=[network])] + async fn ethernet_link(mut c: ethernet_link::Context) -> ! { + loop { + c.shared.network.lock(|net| net.processor.handle_link()); + Systick::delay(1.secs()).await; + } + } + + #[task(binds = ETH, priority = 1)] + fn eth(_: eth::Context) { + unsafe { hal::ethernet::interrupt_handler() } + } +} diff --git a/src/hardware/net.rs b/src/hardware/net.rs index 1edfcca2c..f55a9dafe 100644 --- a/src/hardware/net.rs +++ b/src/hardware/net.rs @@ -6,6 +6,7 @@ //! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines //! related to Stabilizer networking operations. use heapless; +use log::info; use miniconf; use crate::hardware::{SystemTimer, hal::ethernet}; @@ -107,6 +108,8 @@ where net_settings: &NetSettings, metadata: &'static ApplicationMetadata, ) -> Self { + info!("Application: {}", app); + let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)) .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index db32d99b0..1f06f266b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,3 +11,5 @@ pub mod telemetry; pub mod convert; pub mod statistics; + +pub mod mpll; diff --git a/src/mpll.rs b/src/mpll.rs new file mode 100644 index 000000000..a18553661 --- /dev/null +++ b/src/mpll.rs @@ -0,0 +1,187 @@ +use dsp_fixedpoint::Q32; +use dsp_process::{ + Add, Identity, Inplace, Pair, Parallel, Process, Split, Unsplit, +}; +use idsp::iir::{ + BiquadClamp, DirectForm1, pid, + wdf::{Wdf, WdfState}, +}; +use miniconf::Tree; + +pub const BATCH_SIZE: usize = 8; + +#[derive(Debug, Clone, Default)] +pub struct MpllState { + /// Lowpass state + lp: [(((), ([WdfState<2>; 2], (WdfState<2>, WdfState<1>))), ()); 4], + /// Phase clamp + /// + /// Makes the phase wrap monotonic by clamping it. + /// Aids capture/pull-in with external modulation. + /// + /// TODO: Remove this for modulation drive. + clamp: idsp::ClampWrap, + /// PID state + pub iir: DirectForm1, + /// Current output phase + phase: i32, + /// Current LO samples for downconverting the next batch + lo: [[i32; BATCH_SIZE]; 2], + // TODO: investigate matched LO delay: 20 samples +} + +#[derive(Debug, Clone, Tree)] +#[tree(meta(doc, typename))] +pub struct Mpll { + /// Lowpass + #[tree(skip)] + lp: Pair<[Wdf<2, 0xad>; 2], (Wdf<2, 0xad>, Wdf<1, 0xa>), i32>, + /// Input phase offset + phase: i32, + /// PID IIR filter + /// + /// Do not use the iir offset as phase offset. + /// Includes frequency limits. + #[tree(skip)] + iir: BiquadClamp, i32>, + /// Output amplitude scale + amp: [i32; 2], +} + +impl Mpll { + pub fn new(config: &Self) -> Self { + config.clone() + } +} + +impl Default for Mpll { + fn default() -> Self { + // 7th order Cheby2 as WDF-CA, -3 dB @ 0.005*1.28, -112 dB @ 0.018*1.28 + let lp = Pair::new(( + ( + Unsplit(&Identity), + Parallel(( + [ + Wdf::quantize(&[ + -0.9866183703960676, + 0.9995042973541263, + ]) + .unwrap(), + Wdf::quantize(&[-0.94357710177202, 0.9994723555364557]) + .unwrap(), + ], + ( + Wdf::quantize(&[ + -0.9619459355859967, + 0.9994905727027024, + ]) + .unwrap(), + Wdf::quantize(&[0.9677764552414969]).unwrap(), + ), + )), + ), + Unsplit(&Add), + )); + + let mut iir: BiquadClamp<_, _> = pid::Builder::default() + .order(pid::Order::I) + .period(1.0 / BATCH_SIZE as f32) + .gain(pid::Action::P, -5e-3) // fs/turn + .gain(pid::Action::I, -4e-4) // fs/turn/ts = 1/turn + .gain(pid::Action::D, -4e-3) // fs/turn*ts = turn + .limit(pid::Action::D, -0.2) + .into(); + iir.max = (0.3 * (1u64 << 32) as f32) as _; + iir.min = (0.005 * (1u64 << 32) as f32) as _; + + Self { + lp, + phase: (0.0 * (1u64 << 32) as f32) as _, + iir, + amp: [(0.09 * (1u64 << 31) as f32) as _; 2], // ~0.9 V + } + } +} + +impl Mpll { + /// Ingest and process a batch of input samples and output new modulation + pub fn process( + &self, + state: &mut MpllState, + x: [&[u16; BATCH_SIZE]; 2], + y: [&mut [u16; BATCH_SIZE]; 2], + ) -> (Stream, i32) { + // scratch + let mut mix = [[0; BATCH_SIZE]; 4]; + let [m00, m01, m10, m11] = mix.each_mut(); + // mix + for (x, (mix, lo)) in x[0].iter().zip(x[1].iter()).zip( + m00.iter_mut() + .zip(m01.iter_mut()) + .zip(m10.iter_mut().zip(m11.iter_mut())) + .zip(state.lo[0].iter().zip(&state.lo[1])), + ) { + // ADC encoding, mix + *mix.0.0 = ((*x.0 as i16 as i64 * *lo.0 as i64) >> 16) as i32; + *mix.0.1 = ((*x.0 as i16 as i64 * *lo.1 as i64) >> 16) as i32; + *mix.1.0 = ((*x.1 as i16 as i64 * *lo.0 as i64) >> 16) as i32; + *mix.1.1 = ((*x.1 as i16 as i64 * *lo.1 as i64) >> 16) as i32; + } + // lowpass + for (mix, state) in mix.iter_mut().zip(state.lp.iter_mut()) { + Split::new(&self.lp, state).inplace(mix); + } + // decimate + let demod = [ + mix[0][BATCH_SIZE - 1], + mix[1][BATCH_SIZE - 1], + mix[2][BATCH_SIZE - 1], + mix[3][BATCH_SIZE - 1], + ]; + // phase + // need full atan2 or addtl osc to support any phase offset + let p = idsp::atan2(demod[1], demod[0]); + // Delay correction + let p = p + .wrapping_add(self.phase) + .wrapping_sub(state.iir.xy[2].wrapping_mul(10)); + let p = state.clamp.process(p); + // pid + let f = Split::new(&self.iir, &mut state.iir).process(p); + // modulate + let [y0, y1] = state.lo.each_mut(); + let [yo0, yo1] = y; + state.phase = y0 + .iter_mut() + .zip(y1) + .zip((*yo0).iter_mut().zip((*yo1).iter_mut())) + .fold(state.phase, |mut p, (y, yo)| { + p = p.wrapping_add(f); + (*y.0, *y.1) = idsp::cossin(p); + *yo.0 = (((*y.0 as i64 * self.amp[0] as i64) >> (31 + 31 - 15)) + as i16) + .wrapping_add(i16::MIN) as u16; // DAC encoding + *yo.1 = (((*y.1 as i64 * self.amp[1] as i64) >> (31 + 31 - 15)) + as i16) + .wrapping_add(i16::MIN) as u16; // DAC encoding + p + }); + ( + Stream { + demod, + phase_in: p, + phase_out: state.phase, + }, + f, + ) + } +} + +/// Stream data format. +#[derive(Clone, Copy, Debug, Default, bytemuck::Zeroable, bytemuck::Pod)] +#[repr(C)] +pub struct Stream { + pub demod: [i32; 4], + pub phase_in: i32, // after clamping and offset + pub phase_out: i32, // derivative is output frequency +} diff --git a/stream/Cargo.toml b/stream/Cargo.toml index 663fbab74..b43dd014d 100644 --- a/stream/Cargo.toml +++ b/stream/Cargo.toml @@ -11,7 +11,7 @@ keywords = [] [dependencies] serde = { version = "1.0", features = ["derive"], default-features = false } -heapless = { version = "0.8", features = ["serde"] } +heapless.workspace = true num_enum = { version = "0.7.3", default-features = false } serde_with = { version = "3.12", default-features = false, features = [ "macros", diff --git a/stream/src/lib.rs b/stream/src/lib.rs index f7548e9ae..55f60212d 100644 --- a/stream/src/lib.rs +++ b/stream/src/lib.rs @@ -92,6 +92,9 @@ pub enum Format { /// Thermostat-EEM data. See `thermostat-eem` repo and application. ThermostatEem = 3, + + /// MPLL data + Mpll = 4, } #[cfg(target_arch = "arm")] diff --git a/urukul/Cargo.toml b/urukul/Cargo.toml index 8e3cc49b2..cb01e52d3 100644 --- a/urukul/Cargo.toml +++ b/urukul/Cargo.toml @@ -5,16 +5,14 @@ license.workspace = true repository.workspace = true version = "0.1.1" description = "Sinara Urukul quad 1 GHz DDS driver" -authors = [ - "Robert Jördens ", -] +authors = ["Robert Jördens "] categories = ["embedded", "no-std"] keywords = ["spi"] [dependencies] encoded_pin = { version = "0.1", path = "../encoded_pin" } -arbitrary-int = { version = "1.3.0", features = ["serde"] } -thiserror = { version = "2.0.11", default-features = false } +arbitrary-int.workspace = true +thiserror.workspace = true num-traits = { version = "0.2.19", default-features = false } embedded-hal = { version = "1.0" } embedded-hal-bus = "0.3.0"