diff --git a/.github/bors.toml b/.github/bors.toml index 722246d..1733a86 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -1,7 +1,9 @@ block_labels = [ "S-blocked" ] delete_merged_branches = true +timeout_sec = 1200 status = [ "style", "test (stable)", "compile (stable)", + "HITL Run Status" ] diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..9b3dd5c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,4 @@ +# add changes-hitl label if any hitl scripts are changed +# REVIEW those changes before approving HITL deployment! +changes-hitl: + - any: [hitl/*] diff --git a/.github/workflows/hitl.yml b/.github/workflows/hitl.yml deleted file mode 100644 index f59e8d2..0000000 --- a/.github/workflows/hitl.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: HITL - -on: - push: - branches: [ hitl ] - workflow_dispatch: - -jobs: - hitl: - runs-on: ubuntu-latest - environment: hitl - steps: - - uses: peter-evans/repository-dispatch@v1 - with: - token: ${{ secrets.DISPATCH_PAT }} - event-type: stabilizer - repository: quartiq/hitl - client-payload: '{"github": ${{ toJson(github) }}}' diff --git a/.github/workflows/hitl_trigger.yml b/.github/workflows/hitl_trigger.yml new file mode 100644 index 0000000..b0fafac --- /dev/null +++ b/.github/workflows/hitl_trigger.yml @@ -0,0 +1,39 @@ +name: HITL Trigger + +on: + workflow_dispatch: + push: + branches: + - staging + - trying + +jobs: + hitl-trigger: + runs-on: ubuntu-latest + environment: hitl + steps: + - uses: LouisBrunner/checks-action@v1.1.1 + id: hitl-check + with: + repo: ${{ github.repository }} + sha: ${{ github.event.head_commit.id }} + token: ${{ github.token }} + name: HITL Run Status + status: in_progress + details_url: "https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" + output: | + {"summary": "Starting..."} + + - uses: peter-evans/repository-dispatch@v1 + with: + token: ${{ secrets.DISPATCH_PAT }} + event-type: stabilizer + repository: quartiq/hitl + client-payload: | + {"github": ${{ toJson(github) }}, "check_id": ${{steps.hitl-check.outputs.check_id}}} + + - uses: fountainhead/action-wait-for-check@v1.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + checkName: HITL Run Status + ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..82b4e70 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + branches: [master] + +jobs: + labeler: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v3 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a42efa..04aa2fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,73 @@ -# Changelog +# Change Log -## [v0.2.0] 2019-05-28 +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v0.5.0] - 2021-04-21 + +### Added + +* Batch sample processing +* DMA for ADC and DAC batches +* Pounder profile streaming +* DSP library with lots of optimized algorithms +* Digital input support +* Hardware in the loop continuous integration testing +* Dependency updates +* MQTT settings interface through miniconf/minimq +* Multi-binary support +* DHCP support + +### Changed + +* Removed JSON-over-TCP interface + +### Fixed + +* Robust EEPROM MAC address reading slow supply start + +## [v0.4.1] - 2020-06-23 + +### Fixed + +* Fix DAC clr/ldac, SPI speed + +## [v0.4.0] - 2020-06-22 + +### Added + +* Hardware v1.1 only +* AD9959/Pounder support + +### Changed + +* HAL port + +## [v0.3.0] - 2020-01-20 + +### Added + +* Red LED handling +* EEPROM MAC address reading + +### Changed + +* Panic handler cleanup +* Dependency updates (smoltcp, rtfm) + +## [v0.2.0] - 2019-05-28 + +### Added * Initial basic release * Ethernet support @@ -9,6 +76,15 @@ * ADC/DAC timing and interrupts * Board configuration, bootstrap -## [v0.1.0] 2019-03-10 +## [v0.1.0] - 2019-03-10 + +### Added * First bits of code published + +[Unreleased]: https://github.com/quartiq/stabilizer/compare/v0.5.0...HEAD +[v0.5.0]: https://github.com/quartiq/stabilizer/compare/v0.4.1...v0.5.0 +[v0.4.1]: https://github.com/quartiq/stabilizer/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/quartiq/stabilizer/compare/v0.3.0...v0.4.0 +[v0.3.0]: https://github.com/quartiq/stabilizer/compare/v0.2.0...v0.3.0 +[v0.2.0]: https://github.com/quartiq/stabilizer/compare/v0.1.0...v0.2.0 diff --git a/Cargo.lock b/Cargo.lock index d8e7782..4aa5c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ad9959" version = "0.1.0" @@ -36,7 +38,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9a69a963b70ddacfcd382524f72a4576f359af9334b3bf48a79566590bb8bfa" dependencies = [ "bitrate", - "cortex-m 0.7.1", + "cortex-m 0.7.2", "embedded-hal", ] @@ -87,9 +89,9 @@ checksum = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" @@ -121,15 +123,15 @@ dependencies = [ "aligned", "bare-metal 0.2.5", "bitfield", - "cortex-m 0.7.1", + "cortex-m 0.7.2", "volatile-register", ] [[package]] name = "cortex-m" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b756a8bffc56025de45218a48ff9b801180440c0ee49a722b32d49dcebc771" +checksum = "643a210c1bdc23d0db511e2a576082f4ff4dcae9d0c37f50b431b8f8439d6d6b" dependencies = [ "bare-metal 0.2.5", "bitfield", @@ -143,7 +145,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e202d2eac4e34adf7524a563e36623bae6f69cc0a73ef9bd22a4c93a5a806fa" dependencies = [ - "cortex-m 0.7.1", + "cortex-m 0.7.2", "cortex-m-semihosting", "log", ] @@ -198,13 +200,13 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bffa6c1454368a6aa4811ae60964c38e6996d397ff8095a8b9211b1c1f749bc" dependencies = [ - "cortex-m 0.7.1", + "cortex-m 0.7.2", ] [[package]] name = "derive_miniconf" version = "0.1.0" -source = "git+https://github.com/quartiq/miniconf.git?branch=develop#314fa5587d1aa28e1ad70106f19e30db646e9f28" +source = "git+https://github.com/quartiq/miniconf.git?rev=314fa5587d#314fa5587d1aa28e1ad70106f19e30db646e9f28" dependencies = [ "proc-macro2", "quote", @@ -362,9 +364,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown", @@ -372,9 +374,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.86" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "libm" @@ -393,15 +395,15 @@ dependencies = [ [[package]] name = "managed" -version = "0.8.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577" [[package]] name = "matrixmultiply" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +checksum = "1300bdbea33ec2836b01ff1f5a6eed8bad66d0c31f94d9b7993407a8b054c3a1" dependencies = [ "rawpointer", ] @@ -409,7 +411,7 @@ dependencies = [ [[package]] name = "mcp23017" version = "0.1.1" -source = "git+https://github.com/mrd0ll4r/mcp23017.git#61933f857abe5a837800493a5f58e91a3c9435ec" +source = "git+https://github.com/mrd0ll4r/mcp23017.git?rev=61933f857a#61933f857abe5a837800493a5f58e91a3c9435ec" dependencies = [ "embedded-hal", ] @@ -417,7 +419,7 @@ dependencies = [ [[package]] name = "miniconf" version = "0.1.0" -source = "git+https://github.com/quartiq/miniconf.git?branch=develop#314fa5587d1aa28e1ad70106f19e30db646e9f28" +source = "git+https://github.com/quartiq/miniconf.git?rev=314fa5587d#314fa5587d1aa28e1ad70106f19e30db646e9f28" dependencies = [ "derive_miniconf", "heapless 0.6.1", @@ -429,7 +431,7 @@ dependencies = [ [[package]] name = "minimq" version = "0.2.0" -source = "git+https://github.com/quartiq/minimq.git#933687c2e4bc8a4d972de9a4d1508b0b554a8b38" +source = "git+https://github.com/quartiq/minimq.git?rev=933687c2e4b#933687c2e4bc8a4d972de9a4d1508b0b554a8b38" dependencies = [ "bit_field", "embedded-nal", @@ -438,6 +440,12 @@ dependencies = [ "heapless 0.6.1", ] +[[package]] +name = "nanorand" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1378b66f7c93a1c0f8464a19bf47df8795083842e5090f4b7305973d5a22d0" + [[package]] name = "nb" version = "0.1.3" @@ -455,9 +463,9 @@ checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" [[package]] name = "ndarray" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0d5c9540a691d153064dc47a4db2504587a75eae07bf1d73f7a596ebc73c04" +checksum = "cc1372704f14bb132a49a6701c2238970a359ee0829fed481b522a63bf25456a" dependencies = [ "matrixmultiply", "num-complex", @@ -474,9 +482,9 @@ checksum = "2178127478ae4ee9be7180bc9c3bffb6354dd7238400db567102f98c413a9f35" [[package]] name = "num" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-complex", "num-integer", @@ -487,9 +495,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ "num-traits", ] @@ -517,9 +525,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", "num-integer", @@ -541,7 +549,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d55dedd501dfd02514646e0af4d7016ce36bc12ae177ef52056989966a1eec" dependencies = [ - "cortex-m 0.7.1", + "cortex-m 0.7.2", "cortex-m-semihosting", ] @@ -559,9 +567,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] @@ -664,9 +672,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] @@ -674,7 +682,7 @@ dependencies = [ [[package]] name = "serde-json-core" version = "0.2.0" -source = "git+https://github.com/rust-embedded-community/serde-json-core.git?branch=master#ee06ac91bc43b72450a92198a00d9e5c5b9946d2" +source = "git+https://github.com/rust-embedded-community/serde-json-core.git?rev=ee06ac91bc#ee06ac91bc43b72450a92198a00d9e5c5b9946d2" dependencies = [ "heapless 0.5.6", "serde", @@ -682,9 +690,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -693,8 +701,9 @@ dependencies = [ [[package]] name = "smoltcp" -version = "0.7.0" -source = "git+https://github.com/smoltcp-rs/smoltcp.git#43567b9743cb9f422de83fad9ff42a6d13f6e5ee" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97173c1ef35b0a09304cb3882eba594761243005847cbbf6124f966e8da6519a" dependencies = [ "bitflags", "byteorder", @@ -704,16 +713,17 @@ dependencies = [ [[package]] name = "smoltcp-nal" version = "0.1.0" -source = "git+https://github.com/quartiq/smoltcp-nal.git?branch=main#56519012d7c6a382eaa0d7ecb26f2701771d9ce8" +source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=8468f11#8468f11abacd7aba82454e6904df19c1d1ab91bb" dependencies = [ "embedded-nal", "heapless 0.6.1", + "nanorand", "smoltcp", ] [[package]] name = "stabilizer" -version = "0.4.1" +version = "0.5.0" dependencies = [ "ad9959", "asm-delay", @@ -746,24 +756,25 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stm32h7" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7571f17d1ed7d67957d0004de6c52bd1ef5e736ed5ddc2bcecf001512269f77c" +checksum = "8b672c837e0ee8158ecc7fce0f9a948dd0693a9c588338e728d14b73307a0b7d" dependencies = [ "bare-metal 0.2.5", - "cortex-m 0.6.7", + "cortex-m 0.7.2", "cortex-m-rt", "vcell", ] [[package]] name = "stm32h7xx-hal" -version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=master#08231e334a11236fe556668ac19cb1c214da2406" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67034b80041bc33a48df1c1c435b6ae3bb18c35e42aa7e702ce8363b96793398" dependencies = [ "bare-metal 1.0.0", "cast", - "cortex-m 0.6.7", + "cortex-m 0.7.2", "cortex-m-rt", "embedded-dma", "embedded-hal", @@ -776,9 +787,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.60" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" dependencies = [ "proc-macro2", "quote", @@ -787,9 +798,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-xid" @@ -805,9 +816,9 @@ checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "void" diff --git a/Cargo.toml b/Cargo.toml index 3499ac3..806600c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stabilizer" -version = "0.4.1" +version = "0.5.0" authors = ["Robert Jördens "] description = "Firmware for the Sinara Stabilizer board (stm32h743, eth, poe, 2 adc, 2 dac)" categories = ["embedded", "no-std", "hardware-support", "science"] @@ -43,9 +43,16 @@ enum-iterator = "0.6.0" paste = "1" dsp = { path = "dsp" } ad9959 = { path = "ad9959" } -smoltcp-nal = "0.1.0" -miniconf = "0.1" generic-array = "0.14" +miniconf = "0.1.0" + +[dependencies.mcp23017] +git = "https://github.com/mrd0ll4r/mcp23017.git" +rev = "61933f857a" + +[dependencies.stm32h7xx-hal] +features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] +version = "0.9.0" [build-dependencies] cc = "1.0" @@ -56,31 +63,19 @@ rev = "a2e3ad5" [patch.crates-io.miniconf] git = "https://github.com/quartiq/miniconf.git" -branch = "develop" +rev = "314fa5587d" + +[dependencies.smoltcp-nal] +git = "https://github.com/quartiq/smoltcp-nal.git" +rev = "8468f11" [patch.crates-io.minimq] git = "https://github.com/quartiq/minimq.git" - -[patch.crates-io.smoltcp-nal] -git = "https://github.com/quartiq/smoltcp-nal.git" -branch = "main" +rev = "933687c2e4b" [patch.crates-io.serde-json-core] git = "https://github.com/rust-embedded-community/serde-json-core.git" -branch = "master" - -[patch.crates-io.smoltcp] -# We manually patch smoltcp so that we can get access to unreleased updates to the DHCP server. When -# a new release of smoltcp is made, we can remove this patch. -git = "https://github.com/smoltcp-rs/smoltcp.git" - -[dependencies.mcp23017] -git = "https://github.com/mrd0ll4r/mcp23017.git" - -[dependencies.stm32h7xx-hal] -features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/stm32-rs/stm32h7xx-hal" -branch = "master" +rev = "ee06ac91bc" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/README.md b/README.md index 19b5a0d..f91976a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org) -[![HITL (private)](https://github.com/quartiq/hitl/workflows/Stabilizer/badge.svg)](https://github.com/quartiq/hitl/actions?query=workflow%3AStabilizer) +[![Continuous Integration](https://github.com/quartiq/stabilizer/actions/workflows/ci.yml/badge.svg)](https://github.com/quartiq/stabilizer/actions/workflows/ci.yml) +[![Stabilizer HITL [Nightly]](https://github.com/quartiq/hitl/actions/workflows/stabilizer-nightly.yml/badge.svg)](https://github.com/quartiq/hitl/actions/workflows/stabilizer-nightly.yml) # Stabilizer Firmware diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 206d90a..21dfa21 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -558,13 +558,15 @@ impl ProfileSerializer { /// * `channels` - A list of channels to apply the configuration to. /// * `ftw` - If provided, indicates a frequency tuning word for the channels. /// * `pow` - If provided, indicates a phase offset word for the channels. - /// * `acr` - If provided, indicates the amplitude control register for the channels. + /// * `acr` - If provided, indicates the amplitude control register for the channels. The ACR + /// should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used, + /// the "Amplitude multiplier enable" bit must be set. pub fn update_channels( &mut self, channels: &[Channel], ftw: Option, pow: Option, - acr: Option, + acr: Option, ) { let mut csr: u8 = *0u8.set_bits(1..3, self.mode as u8); for channel in channels.iter() { @@ -582,7 +584,7 @@ impl ProfileSerializer { } if let Some(acr) = acr { - self.add_write(Register::ACR, &acr.to_be_bytes()); + self.add_write(Register::ACR, &acr.to_be_bytes()[1..=3]); } } @@ -606,14 +608,14 @@ impl ProfileSerializer { // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and LSRR. let padding = 4 - (self.index % 4); match padding { - 0 => {} 1 => { // For a pad size of 1, we have to pad with 5 bytes to align things. self.add_write(Register::CSR, &[(self.mode as u8) << 1]); - self.add_write(Register::LSRR, &[0, 0, 0]); + self.add_write(Register::LSRR, &[0, 0]); } 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), - 3 => self.add_write(Register::LSRR, &[0, 0, 0]), + 3 => self.add_write(Register::LSRR, &[0, 0]), + 4 => {} _ => unreachable!(), } diff --git a/cargosha256-dual-iir.nix b/cargosha256-dual-iir.nix index d58968f..2d89c3a 100644 --- a/cargosha256-dual-iir.nix +++ b/cargosha256-dual-iir.nix @@ -1 +1 @@ -"138kpxzxs73zhmd4xi5kw3fddb05gac4mpngizm01831n1ycyhl0" +"06qsl59bljr637xcrplbij7ma8l7waryi4lkbd4fxjac0gqpn55s" diff --git a/dsp/Cargo.toml b/dsp/Cargo.toml index 4c675ff..7b680d3 100644 --- a/dsp/Cargo.toml +++ b/dsp/Cargo.toml @@ -8,13 +8,13 @@ edition = "2018" libm = "0.2.1" serde = { version = "1.0", features = ["derive"], default-features = false } generic-array = "0.14" -num = { version = "0.3.1", default-features = false } +num = { version = "0.4.0", default-features = false } miniconf = "0.1" [dev-dependencies] easybench = "1.0" rand = "0.8" -ndarray = "0.14" +ndarray = "0.15" [[bench]] name = "micro" diff --git a/dsp/benches/micro.rs b/dsp/benches/micro.rs index 0902a4c..22a2540 100644 --- a/dsp/benches/micro.rs +++ b/dsp/benches/micro.rs @@ -67,7 +67,7 @@ fn iir_bench() { let mut xy = iir::Vec5::default(); println!( "int::IIR::update(s, x): {}", - bench_env(0.32241, |x| dut.update(&mut xy, *x)) + bench_env(0.32241, |x| dut.update(&mut xy, *x, true)) ); } diff --git a/dsp/src/iir.rs b/dsp/src/iir.rs index ee9639e..a6af191 100644 --- a/dsp/src/iir.rs +++ b/dsp/src/iir.rs @@ -117,7 +117,7 @@ impl IIR { /// # Arguments /// * `xy` - Current filter state. /// * `x0` - New input. - pub fn update(&self, xy: &mut Vec5, x0: f32) -> f32 { + pub fn update(&self, xy: &mut Vec5, x0: f32, hold: bool) -> f32 { let n = self.ba.len(); debug_assert!(xy.len() == n); // `xy` contains x0 x1 y0 y1 y2 @@ -128,7 +128,11 @@ impl IIR { // Store x0 x0 x1 x2 y1 y2 xy[0] = x0; // Compute y0 by multiply-accumulate - let y0 = macc(self.y_offset, xy, &self.ba); + let y0 = if hold { + xy[n / 2 + 1] + } else { + macc(self.y_offset, xy, &self.ba) + }; // Limit y0 let y0 = max(self.y_min, min(self.y_max, y0)); // Store y0 x0 x1 y0 y1 y2 diff --git a/hitl/README.md b/hitl/README.md new file mode 100644 index 0000000..629b587 --- /dev/null +++ b/hitl/README.md @@ -0,0 +1,25 @@ +# Stabilizer HITL Testing + +This directory contains tooling required for Stabilizer hardware-in-the-loop (HITL) testing. + +There is a `Stabilizer` board connected at the Quartiq office that is accessible via a private HITL +repository in order to provide secure hardware testing of the stabilizer application in a public +repository. + +**Note**: In order to ensure application security, all HITL runs must first be approved by a Quartiq +representative before execution. + +# Configuration +* Stabilizer is configured with an ethernet connection to a router. The router runs a DHCP server for +the local network, and ensures that the Stabilizer used for these tests is available under the hostname `stabilizer-hitl`. +* An MQTT broker is running at the hostname `mqtt`. + +# HITL Workflow +The private HITL repository does the following: + +1. Check out this repository +2. Build firmware images using Cargo +3. Program stabilizer +4. Execute `hitl/run.sh` + +In order to add new HITL tests, update `run.sh` to include the necessary tests. diff --git a/hitl/run.sh b/hitl/run.sh new file mode 100755 index 0000000..2af4732 --- /dev/null +++ b/hitl/run.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Title: +# Stabilizer hardware-in-the-loop (HITL) test script. +# +# Description: +# This shell file is executed by the hardware runner in Quartiq's office to exercise the various +# hardware aspects of Stabilizer. + +# Enable shell operating mode flags. +set -eux + +# Set up python for testing +python3 -m venv --system-site-packages py +. py/bin/activate +python3 -m pip install -r requirements.txt + +cargo flash --elf target/thumbv7em-none-eabihf/release/dual-iir --chip STM32H743ZITx + +# Before attempting to ping the device, sleep to allow Stabilizer to boot. +sleep 30 + +# Test pinging Stabilizer. This exercises that: +# * DHCP is functional and an IP has been acquired +# * Stabilizer's network is functioning as intended +# * The stabilizer application is operational +ping -c 5 -w 20 stabilizer-hitl + +# Test the MQTT interface. +python3 miniconf.py dt/sinara/dual-iir/04-91-62-d9-7e-5f afe/0='"G2"' +python3 miniconf.py dt/sinara/dual-iir/04-91-62-d9-7e-5f afe/0='"G1"' iir_ch/0/0=\ +'{"y_min": -32767, "y_max": 32767, "y_offset": 0, "ba": [1.0, 0, 0, 0, 0]}' diff --git a/miniconf.py b/miniconf.py index 019ebcb..b059f78 100644 --- a/miniconf.py +++ b/miniconf.py @@ -83,19 +83,19 @@ class Miniconf: def main(): parser = argparse.ArgumentParser( description='Miniconf command line interface.', - epilog='''Example: - %(prog)s -v -b mqtt dt/sinara/stabilizer afe/0 '"G10"' - ''') + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''Examples: +%(prog)s dt/sinara/stabilizer afe/0='"G2"' iir_ch/0/0=\ +'{"y_min": -32767, "y_max": 32767, "y_offset": 0, "ba": [1.0, 0, 0, 0, 0]}' +''') parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase logging verbosity') parser.add_argument('--broker', '-b', default='mqtt', type=str, help='The MQTT broker address') parser.add_argument('prefix', type=str, help='The MQTT topic prefix of the target') - parser.add_argument('path', type=str, - help='The setting path to configure') - parser.add_argument('value', type=str, - help='The value of setting in JSON format') + parser.add_argument('settings', metavar="KEY=VALUE", nargs='+', + help='JSON encoded values for settings path keys.') args = parser.parse_args() @@ -107,8 +107,10 @@ def main(): async def configure_settings(): interface = await Miniconf.create(args.prefix, args.broker) - response = await interface.command(args.path, json.loads(args.value)) - print(f"Response: {response}") + for kv in args.settings: + path, value = kv.split("=", 1) + response = await interface.command(path, json.loads(value)) + print(response) loop.run_until_complete(configure_settings()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a3d7fef --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +gmqtt diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 5a878dc..0225175 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -2,35 +2,47 @@ #![no_std] #![no_main] -use stm32h7xx_hal as hal; +use stabilizer::{hardware, net}; -use stabilizer::hardware; - -use miniconf::{minimq, Miniconf, MqttInterface}; +use miniconf::Miniconf; use serde::Deserialize; use dsp::iir; use hardware::{ - Adc0Input, Adc1Input, AfeGain, CycleCounter, Dac0Output, Dac1Output, - NetworkStack, AFE0, AFE1, + Adc0Input, Adc1Input, AfeGain, Dac0Output, Dac1Output, DigitalInput1, + InputPin, AFE0, AFE1, }; +use net::{Action, MiniconfInterface}; + 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(Debug, Deserialize, Miniconf)] +#[derive(Clone, Copy, Debug, Deserialize, Miniconf)] pub struct Settings { afe: [AfeGain; 2], iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], + allow_hold: bool, + force_hold: bool, } impl Default for Settings { fn default() -> Self { Self { + // Analog frontend programmable gain amplifier gains (G1, G2, G5, G10) afe: [AfeGain::G1, AfeGain::G1], + // IIR filter tap gains are an array `[b0, b1, b2, a1, a2]` such that the + // new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`. + // The array is `iir_state[channel-index][cascade-index][coeff-index]`. + // The IIR coefficients can be mapped to other transfer function + // representations, for example as described in https://arxiv.org/abs/1508.06319 iir_ch: [[iir::IIR::new(1., -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2], + // Permit the DI1 digital input to suppress filter output updates. + allow_hold: false, + // Force suppress filter output updates. + force_hold: false, } } } @@ -39,41 +51,34 @@ impl Default for Settings { const APP: () = { struct Resources { afes: (AFE0, AFE1), + digital_input1: DigitalInput1, adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - mqtt_interface: - MqttInterface, - clock: CycleCounter, + mqtt_config: MiniconfInterface, - // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] iir_state: [[iir::Vec5; IIR_CASCADE_LENGTH]; 2], - #[init([[iir::IIR::new(1., -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2])] - iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], + settings: Settings, } - #[init] + #[init(spawn=[settings_update])] fn init(c: init::Context) -> init::LateResources { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - let mqtt_interface = { - let mqtt_client = { - minimq::MqttClient::new( - hardware::design_parameters::MQTT_BROKER.into(), - "", - stabilizer.net.stack, - ) - .unwrap() - }; + let mqtt_config = MiniconfInterface::new( + stabilizer.net.stack, + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); - MqttInterface::new( - mqtt_client, - "dt/sinara/stabilizer", - Settings::default(), - ) - .unwrap() - }; + // Spawn a settings update for default settings. + c.spawn.settings_update().unwrap(); // Enable ADC/DAC events stabilizer.adcs.0.start(); @@ -85,11 +90,12 @@ const APP: () = { stabilizer.adc_dac_timer.start(); init::LateResources { - mqtt_interface, afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, - clock: stabilizer.cycle_counter, + mqtt_config, + digital_input1: stabilizer.digital_inputs.1, + settings: Settings::default(), } } @@ -109,7 +115,7 @@ const APP: () = { /// /// Because the ADC and DAC operate at the same rate, these two constraints actually implement /// the same time bounds, meeting one also means the other is also met. - #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, digital_input1, dacs, iir_state, settings], priority=2)] #[inline(never)] #[link_section = ".itcm.process"] fn process(c: process::Context) { @@ -123,13 +129,19 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; + let hold = c.resources.settings.force_hold + || (c.resources.digital_input1.is_high().unwrap() + && c.resources.settings.allow_hold); + for channel in 0..adc_samples.len() { for sample in 0..adc_samples[0].len() { - let x = f32::from(adc_samples[channel][sample] as i16); - let mut y = x; + let mut y = f32::from(adc_samples[channel][sample] as i16); for i in 0..c.resources.iir_state[channel].len() { - y = c.resources.iir_ch[channel][i] - .update(&mut c.resources.iir_state[channel][i], y); + y = c.resources.settings.iir_ch[channel][i].update( + &mut c.resources.iir_state[channel][i], + y, + hold, + ); } // Note(unsafe): The filter limits ensure that the value is in range. // The truncation introduces 1/2 LSB distortion. @@ -140,47 +152,29 @@ const APP: () = { } } - #[idle(resources=[mqtt_interface, clock], spawn=[settings_update])] + #[idle(resources=[mqtt_config], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { - let clock = c.resources.clock; - loop { - let sleep = c.resources.mqtt_interface.lock(|interface| { - match interface.network_stack().poll(clock.current_ms()) { - Ok(updated) => !updated, - Err(err) => { - log::info!("Network error: {:?}", err); - false - } - } - }); - match c .resources - .mqtt_interface - .lock(|interface| interface.update()) + .mqtt_config + .lock(|config_interface| config_interface.update()) { - Ok(update) => { - if update { - c.spawn.settings_update().unwrap(); - } else if sleep { - cortex_m::asm::wfi(); - } + Some(Action::Sleep) => cortex_m::asm::wfi(), + Some(Action::UpdateSettings) => { + c.spawn.settings_update().unwrap() } - Err(miniconf::MqttError::Network( - smoltcp_nal::NetworkError::NoIpAddress, - )) => {} - Err(error) => log::info!("Unexpected error: {:?}", error), + _ => {} } } } - #[task(priority = 1, resources=[mqtt_interface, afes, iir_ch])] + #[task(priority = 1, resources=[mqtt_config, afes, settings])] fn settings_update(mut c: settings_update::Context) { - let settings = &c.resources.mqtt_interface.settings; + let settings = &c.resources.mqtt_config.mqtt.settings; // Update the IIR channels. - c.resources.iir_ch.lock(|iir| *iir = settings.iir_ch); + c.resources.settings.lock(|current| *current = *settings); // Update AFEs c.resources.afes.0.set_gain(settings.afe[0]); @@ -189,7 +183,7 @@ const APP: () = { #[task(binds = ETH, priority = 1)] fn eth(_: eth::Context) { - unsafe { hal::ethernet::interrupt_handler() } + unsafe { stm32h7xx_hal::ethernet::interrupt_handler() } } #[task(binds = SPI2, priority = 3)] diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 7d2db0e..082b0d2 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -4,16 +4,20 @@ use generic_array::typenum::U4; -use miniconf::{minimq, Miniconf, MqttInterface}; use serde::Deserialize; use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; +use stabilizer::net; + use stabilizer::hardware::{ - design_parameters, setup, Adc0Input, Adc1Input, AfeGain, CycleCounter, - Dac0Output, Dac1Output, InputStamper, NetworkStack, AFE0, AFE1, + design_parameters, setup, Adc0Input, Adc1Input, AfeGain, Dac0Output, + Dac1Output, InputStamper, AFE0, AFE1, }; +use miniconf::Miniconf; +use stabilizer::net::{Action, MiniconfInterface}; + #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { PowerPhase, @@ -56,11 +60,7 @@ const APP: () = { afes: (AFE0, AFE1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - clock: CycleCounter, - - mqtt_interface: - MqttInterface, - + mqtt_config: MiniconfInterface, settings: Settings, timestamper: InputStamper, @@ -73,21 +73,16 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = setup(c.core, c.device); - let mqtt_interface = { - let mqtt_client = minimq::MqttClient::new( - design_parameters::MQTT_BROKER.into(), - "", - stabilizer.net.stack, - ) - .unwrap(); - - MqttInterface::new( - mqtt_client, - "dt/sinara/lockin", - Settings::default(), - ) - .unwrap() - }; + let mqtt_config = MiniconfInterface::new( + stabilizer.net.stack, + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); let settings = Settings::default(); @@ -118,10 +113,8 @@ const APP: () = { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, + mqtt_config, timestamper: stabilizer.timestamper, - clock: stabilizer.cycle_counter, - - mqtt_interface, settings, @@ -202,44 +195,26 @@ const APP: () = { } } - #[idle(resources=[mqtt_interface, clock], spawn=[settings_update])] + #[idle(resources=[mqtt_config], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { - let clock = c.resources.clock; - loop { - let sleep = c.resources.mqtt_interface.lock(|interface| { - match interface.network_stack().poll(clock.current_ms()) { - Ok(updated) => !updated, - Err(err) => { - log::info!("Network error: {:?}", err); - false - } - } - }); - match c .resources - .mqtt_interface - .lock(|interface| interface.update()) + .mqtt_config + .lock(|config_interface| config_interface.update()) { - Ok(update) => { - if update { - c.spawn.settings_update().unwrap(); - } else if sleep { - cortex_m::asm::wfi(); - } + Some(Action::Sleep) => cortex_m::asm::wfi(), + Some(Action::UpdateSettings) => { + c.spawn.settings_update().unwrap() } - Err(miniconf::MqttError::Network( - smoltcp_nal::NetworkError::NoIpAddress, - )) => {} - Err(error) => log::info!("Unexpected error: {:?}", error), + _ => {} } } } - #[task(priority = 1, resources=[mqtt_interface, settings, afes])] + #[task(priority = 1, resources=[mqtt_config, settings, afes])] fn settings_update(mut c: settings_update::Context) { - let settings = &c.resources.mqtt_interface.settings; + let settings = &c.resources.mqtt_config.mqtt.settings; c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index dfab1e0..f453c11 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -13,8 +13,8 @@ use embedded_hal::digital::v2::{InputPin, OutputPin}; use super::{ adc, afe, cycle_counter::CycleCounter, dac, design_parameters, - digital_input_stamper, eeprom, pounder, timers, DdsOutput, NetworkStack, - AFE0, AFE1, + digital_input_stamper, eeprom, pounder, timers, DdsOutput, DigitalInput0, + DigitalInput1, EthernetPhy, NetworkStack, AFE0, AFE1, }; pub struct NetStorage { @@ -56,7 +56,8 @@ impl NetStorage { /// The available networking devices on Stabilizer. pub struct NetworkDevices { pub stack: NetworkStack, - pub phy: ethernet::phy::LAN8742A, + pub phy: EthernetPhy, + pub mac_address: smoltcp::wire::EthernetAddress, } /// The available hardware interfaces on Stabilizer. @@ -69,6 +70,7 @@ pub struct StabilizerDevices { pub timestamp_timer: timers::TimestampTimer, pub net: NetworkDevices, pub cycle_counter: CycleCounter, + pub digital_inputs: (DigitalInput0, DigitalInput1), } /// The available Pounder-specific hardware interfaces. @@ -439,6 +441,12 @@ pub fn setup( ) }; + let digital_inputs = { + let di0 = gpiog.pg9.into_floating_input(); + let di1 = gpioc.pc15.into_floating_input(); + (di0, di1) + }; + let mut eeprom_i2c = { let sda = gpiof.pf0.into_alternate_af4().set_open_drain(); let scl = gpiof.pf1.into_alternate_af4().set_open_drain(); @@ -495,13 +503,10 @@ pub fn setup( .set_speed(hal::gpio::Speed::VeryHigh); } - let mac_addr = match eeprom::read_eui48(&mut eeprom_i2c) { - Err(_) => { - info!("Could not read EEPROM, using default MAC address"); - smoltcp::wire::EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]) - } - Ok(raw_mac) => smoltcp::wire::EthernetAddress(raw_mac), - }; + let mac_addr = smoltcp::wire::EthernetAddress(eeprom::read_eui48( + &mut eeprom_i2c, + &mut delay, + )); let network_devices = { // Configure the ethernet controller @@ -594,14 +599,27 @@ pub fn setup( ) }; + let random_seed = { + let mut rng = + device.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks); + let mut data = [0u8; 4]; + rng.fill(&mut data).unwrap(); + data + }; + + let mut stack = smoltcp_nal::NetworkStack::new( + interface, + sockets, + &handles, + Some(dhcp_client), + ); + + stack.seed_random_port(&random_seed); + NetworkDevices { - stack: smoltcp_nal::NetworkStack::new( - interface, - sockets, - &handles, - Some(dhcp_client), - ), + stack, phy: lan8742a, + mac_address: mac_addr, } }; @@ -875,6 +893,7 @@ pub fn setup( adc_dac_timer: sampling_timer, timestamp_timer, cycle_counter, + digital_inputs, }; // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); diff --git a/src/hardware/eeprom.rs b/src/hardware/eeprom.rs index d84dd70..b9b237c 100644 --- a/src/hardware/eeprom.rs +++ b/src/hardware/eeprom.rs @@ -1,12 +1,42 @@ -use embedded_hal::blocking::i2c::WriteRead; +use embedded_hal::blocking::{delay::DelayMs, i2c::WriteRead}; +// The EEPROM is a variant without address bits, so the 3 LSB of this word are "dont-cares". const I2C_ADDR: u8 = 0x50; -pub fn read_eui48(i2c: &mut T) -> Result<[u8; 6], T::Error> +// The MAC address is stored in the last 6 bytes of the 256 byte address space. +const MAC_POINTER: u8 = 0xFA; + +pub fn read_eui48(i2c: &mut T, delay: &mut impl DelayMs) -> [u8; 6] where T: WriteRead, { - let mut buffer = [0u8; 6]; - i2c.write_read(I2C_ADDR, &[0xFA_u8], &mut buffer)?; - Ok(buffer) + let mut previous_read: Option<[u8; 6]> = None; + // On Stabilizer v1.1 and earlier hardware, there is a fault where the I2C bus is not connected + // to the CPU until the P12V0A rail enables, which can take many seconds, or may never come up + // at all. During these transient turn-on conditions, we may fail the I2C read operation. To + // accomodate this, we repeat the I2C read for a set number of attempts with a fixed delay + // between them. Then, we wait for the bus to stabilize by waiting until the MAC address + // read-out is identical for two consecutive reads. + for _ in 0..40 { + let mut buffer = [0u8; 6]; + if i2c + .write_read(I2C_ADDR, &[MAC_POINTER], &mut buffer) + .is_ok() + { + if let Some(old_read) = previous_read { + if old_read == buffer { + return buffer; + } + } + + previous_read.replace(buffer); + } else { + // Remove any pending previous read if we failed the last attempt. + previous_read.take(); + } + + delay.delay_ms(100); + } + + panic!("Failed to read MAC address"); } diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index a83c9d6..8eed38d 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -1,6 +1,9 @@ ///! Module for all hardware-specific setup of Stabilizer use stm32h7xx_hal as hal; +// Re-export for the DigitalInputs below: +pub use embedded_hal::digital::v2::InputPin; + #[cfg(feature = "semihosting")] use panic_semihosting as _; @@ -34,12 +37,22 @@ pub type AFE1 = afe::ProgrammableGainAmplifier< hal::gpio::gpiod::PD15>, >; +// Type alias for digital input 0 (DI0). +pub type DigitalInput0 = + hal::gpio::gpiog::PG9>; + +// Type alias for digital input 1 (DI1). +pub type DigitalInput1 = + hal::gpio::gpioc::PC15>; + pub type NetworkStack = smoltcp_nal::NetworkStack< 'static, 'static, hal::ethernet::EthernetDMA<'static>, >; +pub type EthernetPhy = hal::ethernet::phy::LAN8742A; + pub use configuration::{setup, PounderDevices, StabilizerDevices}; #[inline(never)] diff --git a/src/hardware/pounder/dds_output.rs b/src/hardware/pounder/dds_output.rs index c7c5e6d..e755482 100644 --- a/src/hardware/pounder/dds_output.rs +++ b/src/hardware/pounder/dds_output.rs @@ -144,14 +144,15 @@ impl<'a> ProfileBuilder<'a> { /// * `channels` - A list of channels to apply the configuration to. /// * `ftw` - If provided, indicates a frequency tuning word for the channels. /// * `pow` - If provided, indicates a phase offset word for the channels. - /// * `acr` - If provided, indicates the amplitude control register for the channels. + /// * `acr` - If provided, indicates the amplitude control register for the channels. The + /// 24-bits of the ACR should be stored in the last 3 LSB. #[allow(dead_code)] pub fn update_channels( mut self, channels: &[Channel], ftw: Option, pow: Option, - acr: Option, + acr: Option, ) -> Self { self.serializer.update_channels(channels, ftw, pow, acr); self diff --git a/src/hardware/pounder/mod.rs b/src/hardware/pounder/mod.rs index 0497a37..32a8e80 100644 --- a/src/hardware/pounder/mod.rs +++ b/src/hardware/pounder/mod.rs @@ -231,14 +231,16 @@ impl ad9959::Interface for QspiInterface { }; self.qspi - .write(encoded_address, &encoded_payload) + .write(encoded_address.into(), &encoded_payload) .map_err(|_| Error::Qspi) } ad9959::Mode::FourBitSerial => { if self.streaming { Err(Error::Qspi) } else { - self.qspi.write(addr, data).map_err(|_| Error::Qspi)?; + self.qspi + .write(addr.into(), data) + .map_err(|_| Error::Qspi)?; Ok(()) } } @@ -257,7 +259,7 @@ impl ad9959::Interface for QspiInterface { } self.qspi - .read(0x80_u8 | addr, dest) + .read((0x80_u8 | addr).into(), dest) .map_err(|_| Error::Qspi) } } diff --git a/src/lib.rs b/src/lib.rs index 0c9bf2a..975b08f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ extern crate log; pub mod hardware; +pub mod net; diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..13b514c --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,148 @@ +use crate::hardware::{ + design_parameters::MQTT_BROKER, CycleCounter, EthernetPhy, NetworkStack, +}; + +use core::fmt::Write; + +use heapless::{consts, String}; +use miniconf::minimq; + +/// Potential actions for firmware to take. +pub enum Action { + /// Indicates that firmware can sleep for the next event. + Sleep, + + /// Indicates that settings have updated and firmware needs to propogate changes. + UpdateSettings, +} + +/// MQTT settings interface. +pub struct MiniconfInterface +where + S: miniconf::Miniconf + Default, +{ + pub mqtt: miniconf::MqttInterface, + clock: CycleCounter, + phy: EthernetPhy, + network_was_reset: bool, +} + +impl MiniconfInterface +where + S: miniconf::Miniconf + Default, +{ + /// Construct a new MQTT settings interface. + /// + /// # Args + /// * `stack` - The network stack to use for communication. + /// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning. + /// * `prefix` - The MQTT device prefix to use for this device. + /// * `phy` - The PHY driver for querying the link state. + /// * `clock` - The clock to utilize for querying the current system time. + pub fn new( + stack: NetworkStack, + client_id: &str, + prefix: &str, + phy: EthernetPhy, + clock: CycleCounter, + ) -> Self { + let mqtt = { + let mqtt_client = { + minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) + .unwrap() + }; + + miniconf::MqttInterface::new(mqtt_client, prefix, S::default()) + .unwrap() + }; + + Self { + mqtt, + clock, + phy, + network_was_reset: false, + } + } + + /// Update the MQTT interface and service the network + /// + /// # Returns + /// An option containing an action that should be completed as a result of network servicing. + pub fn update(&mut self) -> Option { + let now = self.clock.current_ms(); + + // First, service the network stack to process and inbound and outbound traffic. + let sleep = match self.mqtt.network_stack().poll(now) { + Ok(updated) => !updated, + Err(err) => { + log::info!("Network error: {:?}", err); + false + } + }; + + // If the PHY indicates there's no more ethernet link, reset the DHCP server in the network + // stack. + if self.phy.poll_link() == false { + // Only reset the network stack once per link reconnection. This prevents us from + // sending an excessive number of DHCP requests. + if !self.network_was_reset { + self.network_was_reset = true; + self.mqtt.network_stack().handle_link_reset(); + } + } else { + self.network_was_reset = false; + } + + // Finally, service the MQTT interface and handle any necessary messages. + match self.mqtt.update() { + Ok(true) => Some(Action::UpdateSettings), + Ok(false) if sleep => Some(Action::Sleep), + Ok(_) => None, + + Err(miniconf::MqttError::Network( + smoltcp_nal::NetworkError::NoIpAddress, + )) => None, + + Err(error) => { + log::info!("Unexpected error: {:?}", error); + None + } + } + } +} + +/// Get the MQTT prefix of a device. +/// +/// # Args +/// * `app` - The name of the application that is executing. +/// * `mac` - The ethernet MAC address of the device. +/// +/// # Returns +/// The MQTT prefix used for this device. +pub fn get_device_prefix( + app: &str, + mac: smoltcp_nal::smoltcp::wire::EthernetAddress, +) -> String { + let mac_string = { + let mut mac_string: String = String::new(); + let mac = mac.as_bytes(); + + // Note(unwrap): 32-bytes is guaranteed to be valid for any mac address, as the address has + // a fixed length. + write!( + &mut mac_string, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ) + .unwrap(); + + mac_string + }; + + // Note(unwrap): The mac address + binary name must be short enough to fit into this string. If + // they are defined too long, this will panic and the device will fail to boot. + let mut prefix: String = String::new(); + write!(&mut prefix, "dt/sinara/{}/{}", app, mac_string).unwrap(); + + prefix +}