Merge branch 'master' into rj/itcm

* master: (70 commits)
  Updating JSON syntax
  Updating trigger
  Adding bors timeout
  dual-iir: add some mqtt parameter docs
  changelog: spelling
  update lock
  let bors handle hitl
  prepare v0.5.0 release
  Updating code after review
  Adding comment, reverting change
  Adding sleep to HITL run
  Updating ping deadline
  Reverting unintended change
  Updating dependency revision
  Renaming interface to avoid confusion
  dual-iir: use InputPin re-export, rename digital_input
  Updating dependency
  dual-iir: add enable_hold, force_hold
  fix bench
  hardware: add digital input support
  ...
master
Robert Jördens 2021-04-29 17:28:58 +02:00
commit f0c50d9678
27 changed files with 633 additions and 262 deletions

2
.github/bors.toml vendored
View File

@ -1,7 +1,9 @@
block_labels = [ "S-blocked" ]
delete_merged_branches = true
timeout_sec = 1200
status = [
"style",
"test (stable)",
"compile (stable)",
"HITL Run Status"
]

4
.github/labeler.yml vendored Normal file
View File

@ -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/*]

View File

@ -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) }}}'

39
.github/workflows/hitl_trigger.yml vendored Normal file
View File

@ -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 }}

12
.github/workflows/labeler.yml vendored Normal file
View File

@ -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 }}"

View File

@ -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

115
Cargo.lock generated
View File

@ -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"

View File

@ -1,6 +1,6 @@
[package]
name = "stabilizer"
version = "0.4.1"
version = "0.5.0"
authors = ["Robert Jördens <rj@quartiq.de>"]
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"]

View File

@ -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

View File

@ -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<u32>,
pow: Option<u16>,
acr: Option<u16>,
acr: Option<u32>,
) {
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!(),
}

View File

@ -1 +1 @@
"138kpxzxs73zhmd4xi5kw3fddb05gac4mpngizm01831n1ycyhl0"
"06qsl59bljr637xcrplbij7ma8l7waryi4lkbd4fxjac0gqpn55s"

View File

@ -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"

View File

@ -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))
);
}

View File

@ -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

25
hitl/README.md Normal file
View File

@ -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.

32
hitl/run.sh Executable file
View File

@ -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]}'

View File

@ -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())

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
gmqtt

View File

@ -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<Settings, NetworkStack, minimq::consts::U256>,
clock: CycleCounter,
mqtt_config: MiniconfInterface<Settings>,
// 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)]

View File

@ -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<Settings, NetworkStack, minimq::consts::U256>,
mqtt_config: MiniconfInterface<Settings>,
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]);

View File

@ -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<ethernet::EthernetMAC>,
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());

View File

@ -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<T>(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<T>(i2c: &mut T, delay: &mut impl DelayMs<u8>) -> [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");
}

View File

@ -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<hal::gpio::Output<hal::gpio::PushPull>>,
>;
// Type alias for digital input 0 (DI0).
pub type DigitalInput0 =
hal::gpio::gpiog::PG9<hal::gpio::Input<hal::gpio::Floating>>;
// Type alias for digital input 1 (DI1).
pub type DigitalInput1 =
hal::gpio::gpioc::PC15<hal::gpio::Input<hal::gpio::Floating>>;
pub type NetworkStack = smoltcp_nal::NetworkStack<
'static,
'static,
hal::ethernet::EthernetDMA<'static>,
>;
pub type EthernetPhy = hal::ethernet::phy::LAN8742A<hal::ethernet::EthernetMAC>;
pub use configuration::{setup, PounderDevices, StabilizerDevices};
#[inline(never)]

View File

@ -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<u32>,
pow: Option<u16>,
acr: Option<u16>,
acr: Option<u32>,
) -> Self {
self.serializer.update_channels(channels, ftw, pow, acr);
self

View File

@ -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)
}
}

View File

@ -5,3 +5,4 @@
extern crate log;
pub mod hardware;
pub mod net;

148
src/net/mod.rs Normal file
View File

@ -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<S>
where
S: miniconf::Miniconf + Default,
{
pub mqtt: miniconf::MqttInterface<S, NetworkStack, minimq::consts::U256>,
clock: CycleCounter,
phy: EthernetPhy,
network_was_reset: bool,
}
impl<S> MiniconfInterface<S>
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<Action> {
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<consts::U128> {
let mac_string = {
let mut mac_string: String<consts::U32> = 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<consts::U128> = String::new();
write!(&mut prefix, "dt/sinara/{}/{}", app, mac_string).unwrap();
prefix
}