Compare commits
19 Commits
e67a37ebbd
...
09686e6940
Author | SHA1 | Date |
---|---|---|
occheung | 09686e6940 | |
occheung | ab4749118c | |
occheung | b4d425dc37 | |
occheung | 93f3b2dfb3 | |
occheung | f993ee5e28 | |
occheung | e2371af952 | |
occheung | 10d265b480 | |
occheung | e17cc27cbb | |
occheung | 4438032772 | |
occheung | a855e9f699 | |
occheung | 26c987bd04 | |
occheung | d462f065a9 | |
occheung | 3211261488 | |
occheung | 3edc4c6957 | |
occheung | 01a66a58b3 | |
occheung | bd80274e86 | |
occheung | 2ef6bb393c | |
occheung | c0e0c381ec | |
occheung | c935236d68 |
|
@ -9,18 +9,6 @@ dependencies = [
|
|||
"as-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0ffd3d69bd89910509a5d31d1f1353f38ccffdd116dd0099bbd6627f7bd8ad8"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.1.3"
|
||||
|
@ -65,35 +53,12 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fa7899958f4aa3c40edc1b033d0e956763319e398924abb80a0034dda5bb198"
|
||||
dependencies = [
|
||||
"cargo-lock",
|
||||
"git2",
|
||||
"semver 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-lock"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8504b63dd1249fd1745b7b4ef9b6f7b107ddeb3c95370043c7dbcc38653a2679"
|
||||
dependencies = [
|
||||
"semver 0.9.0",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.2.3"
|
||||
|
@ -103,15 +68,6 @@ dependencies = [
|
|||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
|
@ -228,31 +184,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firmware"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cortex-m",
|
||||
"cortex-m-log",
|
||||
"cortex-m-rt",
|
||||
"cortex-m-rtic",
|
||||
"embedded-hal",
|
||||
"embedded-nal",
|
||||
"heapless",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"minimq",
|
||||
"nb 1.0.0",
|
||||
"nom",
|
||||
"panic-halt",
|
||||
"panic-itm",
|
||||
"scpi",
|
||||
"smoltcp",
|
||||
"stm32h7xx-hal",
|
||||
"uom 0.29.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.3"
|
||||
|
@ -281,19 +212,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d97249f21e9542caeee9f8e1d150905cd875bf723f5ff771bdb4852eb83a24"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.1.1"
|
||||
|
@ -311,9 +229,9 @@ checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
|
|||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0"
|
||||
checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
"generic-array 0.13.2",
|
||||
|
@ -322,14 +240,25 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
|
||||
name = "humpback-dds"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"cortex-m",
|
||||
"cortex-m-log",
|
||||
"cortex-m-rt",
|
||||
"cortex-m-rtic",
|
||||
"embedded-hal",
|
||||
"embedded-nal",
|
||||
"heapless",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"minimq",
|
||||
"nb 1.0.0",
|
||||
"nom",
|
||||
"panic-halt",
|
||||
"panic-itm",
|
||||
"smoltcp",
|
||||
"stm32h7xx-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -342,15 +271,6 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -360,52 +280,6 @@ dependencies = [
|
|||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.12+1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0100ae90655025134424939f1f60e27e879460d451dff6afedde4f8226cbebfc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af67924b8dd885cccea261866c8ce5b74d239d272e154053ff927dae839f5ae9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
|
@ -421,12 +295,6 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
|
@ -476,36 +344,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "panic-halt"
|
||||
version = "0.2.0"
|
||||
|
@ -540,18 +378,6 @@ dependencies = [
|
|||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.18"
|
||||
|
@ -605,31 +431,7 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scpi"
|
||||
version = "0.3.4"
|
||||
source = "git+https://github.com/occheung/scpi-rs?branch=issue-4#982099ea490bfef70455e995700411746c55ade0"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"arrayvec",
|
||||
"built",
|
||||
"lexical-core",
|
||||
"libm",
|
||||
"scpi_derive",
|
||||
"uom 0.28.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scpi_derive"
|
||||
version = "0.3.4"
|
||||
source = "git+https://github.com/occheung/scpi-rs?branch=issue-4#982099ea490bfef70455e995700411746c55ade0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -637,16 +439,6 @@ name = "semver"
|
|||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "394cec28fa623e00903caf7ba4fa6fb9a0e260280bb8cdbbba029611108a0190"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
@ -657,31 +449,10 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a"
|
||||
source = "git+https://github.com/smoltcp-rs/smoltcp.git#bdfa44270e9c59b3095b555cdf14601f7dc27794"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
|
@ -742,95 +513,24 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
dependencies = [
|
||||
"matches",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "uom"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "627142a1043c2d460613232ce4f7e322e756636e000c0f1d1f2e779cb431358a"
|
||||
dependencies = [
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uom"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcell"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
|
|
47
Cargo.toml
47
Cargo.toml
|
@ -2,7 +2,7 @@
|
|||
authors = ["occheung"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
name = "firmware"
|
||||
name = "humpback-dds"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
|
@ -16,8 +16,7 @@ nb = "1.0.0"
|
|||
|
||||
embedded-nal = "0.1.0"
|
||||
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
||||
heapless = "0.5.5"
|
||||
arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] }
|
||||
heapless = "0.5.6"
|
||||
nom = { version = "5.1.2", default-features = false, features = [] }
|
||||
|
||||
# Logging and Panicking
|
||||
|
@ -27,46 +26,8 @@ cortex-m-log = { version = "0.6.2", features = [ "itm", "log-integration" ] }
|
|||
log = {version = "0.4.11"}
|
||||
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
||||
|
||||
[dependencies.scpi]
|
||||
git = "https://github.com/occheung/scpi-rs"
|
||||
branch = "issue-4"
|
||||
default-features = false
|
||||
features = [ "build-info", "unit-frequency", "unit-angle" ]
|
||||
|
||||
[dependencies.uom]
|
||||
version = "0.29.0"
|
||||
default-features = false
|
||||
features = [ "autoconvert", "f32", "f64", "si" ]
|
||||
|
||||
# Use below SCPI dependency when need to modify SCPI fork offline
|
||||
# [dependencies.scpi]
|
||||
# path = "../scpi-fork/scpi"
|
||||
# default-features = false
|
||||
# features = [ "build-info", "unit-frequency" ]
|
||||
|
||||
[[example]]
|
||||
name = "ethernet"
|
||||
|
||||
[[example]]
|
||||
name = "fpga_config"
|
||||
|
||||
[[example]]
|
||||
name = "tcp_client"
|
||||
|
||||
[[example]]
|
||||
name = "mqtt_client"
|
||||
|
||||
[[example]]
|
||||
name = "mqtt_hello_world"
|
||||
|
||||
# Uncomment for the allocator example.
|
||||
# alloc-cortex-m = "0.3.5"
|
||||
|
||||
# this lets you use `cargo fix`!
|
||||
[[bin]]
|
||||
name = "firmware"
|
||||
test = false
|
||||
bench = false
|
||||
[patch.crates-io]
|
||||
smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # better optimizations
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
Command::new("python3")
|
||||
.arg("migen/fpga_config.py")
|
||||
.spawn()
|
||||
.expect("FPGA bitstream file cannot be built!");
|
||||
|
||||
println!("cargo:rerun-if-changed=migen/fpga_config.py")
|
||||
}
|
|
@ -222,9 +222,10 @@ fn main() -> ! {
|
|||
client.network_stack.update(time);
|
||||
}
|
||||
|
||||
// Process MQTT messages about Urukul/Control
|
||||
let connection = client
|
||||
.poll(|_client, topic, message, _properties| {
|
||||
info!("On '{:?}', received: {:?}", topic, message);
|
||||
info!("On {:?}, received: {:?}", topic, message);
|
||||
// Why is topic a string while message is a slice?
|
||||
mqtt_mux.process_mqtt(topic, message);
|
||||
}).is_ok();
|
||||
|
|
|
@ -10,6 +10,9 @@ from migen.genlib.io import *
|
|||
class UrukulConnector(Module):
|
||||
def __init__(self, platform):
|
||||
# Include extension
|
||||
spi_mosi = [
|
||||
("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33"))
|
||||
]
|
||||
spi_cs = [
|
||||
("spi_cs", 0, Pins("B13 B14 B15"), IOStandard("LVCMOS33"))
|
||||
]
|
||||
|
@ -20,6 +23,7 @@ class UrukulConnector(Module):
|
|||
# Add extensions
|
||||
platform.add_extension(spi_cs)
|
||||
platform.add_extension(io_update)
|
||||
platform.add_extension(spi_mosi)
|
||||
|
||||
# Request EEM I/O & SPI
|
||||
eem0 = [
|
||||
|
@ -34,12 +38,13 @@ class UrukulConnector(Module):
|
|||
platform.request("eem0", 6)
|
||||
]
|
||||
spi = platform.request("spi")
|
||||
spi_mosi = platform.request("spi_mosi")
|
||||
spi_cs = platform.request("spi_cs")
|
||||
led = platform.request("user_led")
|
||||
io_update = platform.request("io_update")
|
||||
|
||||
assert len(spi.clk) == 1
|
||||
assert len(spi.mosi) == 1
|
||||
assert len(spi_mosi) == 1
|
||||
assert len(spi.miso) == 1
|
||||
assert len(spi_cs) == 3
|
||||
assert len(io_update) == 1
|
||||
|
@ -61,8 +66,8 @@ class UrukulConnector(Module):
|
|||
eem0[0].p.eq(spi.clk),
|
||||
eem0[0].n.eq(~spi.clk),
|
||||
|
||||
eem0[1].p.eq(spi.mosi),
|
||||
eem0[1].n.eq(~spi.mosi),
|
||||
eem0[1].p.eq(spi_mosi),
|
||||
eem0[1].n.eq(~spi_mosi),
|
||||
|
||||
spi.miso.eq(~self.miso_n),
|
||||
|
||||
|
|
14
shell.nix
14
shell.nix
|
@ -10,7 +10,7 @@ let
|
|||
|
||||
runOpenOcd = writeShellScriptBin "run-openocd" ''
|
||||
openocd \
|
||||
-f board/st_nucleo_h743zi.cfg \
|
||||
-f openocd/openocd.cfg \
|
||||
-c init &
|
||||
sleep 1
|
||||
'';
|
||||
|
@ -49,12 +49,6 @@ let
|
|||
&& flash-fpga-config
|
||||
'';
|
||||
|
||||
verifyFPGAConfig = writeShellScriptBin "verify-fpga-config" ''
|
||||
gdb -x gdb_config/fpga_verify.gdb
|
||||
diff build/top.bin mem.bin
|
||||
rm mem.bin
|
||||
'';
|
||||
|
||||
resetFlash = writeShellScriptBin "reset-flash" ''
|
||||
gdb -batch -x gdb_config/reset.gdb
|
||||
echo "Reset is complete, please reset the openocd server."
|
||||
|
@ -64,6 +58,10 @@ let
|
|||
openocd -f openocd/openocd.cfg -f openocd/$1.cfg
|
||||
'';
|
||||
|
||||
publishMqtt = writeShellScriptBin "publish-mqtt" ''
|
||||
mosquitto_pub -h localhost -t $1 -m "$2" -d
|
||||
'';
|
||||
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
name = "nix-shell";
|
||||
|
@ -85,8 +83,8 @@ in
|
|||
compileMigenScript
|
||||
flashFPGAConfig
|
||||
configureFPGA
|
||||
verifyFPGAConfig
|
||||
resetFlash
|
||||
openocdFlash
|
||||
publishMqtt
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use embedded_hal::blocking::spi::Transfer;
|
||||
use core::assert;
|
||||
|
||||
use crate::Error;
|
||||
use crate::urukul::Error;
|
||||
|
||||
pub struct Attenuator<SPI> {
|
||||
spi: SPI,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use embedded_hal::blocking::spi::Transfer;
|
||||
use crate::Error;
|
||||
use crate::urukul::Error;
|
||||
use core::mem::size_of;
|
||||
|
||||
// Bitmasks for CFG
|
||||
|
@ -75,7 +75,6 @@ where
|
|||
|
||||
/*
|
||||
* Return selected configuration field
|
||||
* TODO: Return result type instead for error checking
|
||||
*/
|
||||
pub fn get_configuration(&mut self, config_type: CFGMask) -> u8 {
|
||||
config_type.get_filtered_content(self.data) as u8
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Error;
|
||||
use crate::urukul::Error;
|
||||
use crate::spi_slave::Parts;
|
||||
|
||||
use embedded_hal::{
|
||||
|
|
269
src/dds.rs
269
src/dds.rs
|
@ -1,8 +1,9 @@
|
|||
use embedded_hal::blocking::spi::Transfer;
|
||||
use crate::Error;
|
||||
use crate::urukul::Error;
|
||||
use core::mem::size_of;
|
||||
use core::convert::TryInto;
|
||||
use arrayvec::ArrayVec;
|
||||
use heapless::Vec;
|
||||
use heapless::consts::*;
|
||||
|
||||
/*
|
||||
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
||||
|
@ -64,6 +65,8 @@ construct_bitmask!(DDSCFRMask; u32;
|
|||
const WRITE_MASK :u8 = 0x00;
|
||||
const READ_MASK :u8 = 0x80;
|
||||
|
||||
static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new());
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum RAMDestination {
|
||||
Frequency = 0,
|
||||
|
@ -483,94 +486,167 @@ where
|
|||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile, but with RAM data generated by a closure
|
||||
* Configure a RAM mode profile, wrt supplied frequency data
|
||||
* This will setup the static RAM_VEC by converting frequency to ftw
|
||||
*/
|
||||
pub fn set_ram_profile_with_closure<F>(&mut self, profile: u8, start_addr: u16,
|
||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
||||
op_mode: RAMOperationMode, playback_rate: f64, f: F) -> Result<(), Error<E>>
|
||||
where
|
||||
F: FnOnce() -> ArrayVec::<[f64; 2048]>
|
||||
{
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile < 7);
|
||||
assert!(start_addr < 1024);
|
||||
let mut vec = f();
|
||||
if (ram_dst != RAMDestination::Polar && ((vec.len() as u16) + start_addr) < 1024) ||
|
||||
((((vec.len()/2) as u16) + start_addr) < 1024) {
|
||||
return Err(Error::DDSRAMError);
|
||||
}
|
||||
|
||||
// TODO: Convert argument into bytes for RAM
|
||||
let mut byte_vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
||||
match ram_dst {
|
||||
RAMDestination::Frequency => {
|
||||
for freq in vec.into_iter() {
|
||||
let ftw = self.frequency_to_ftw(freq);
|
||||
byte_vec.push(((ftw >> 24) & 0xFF) as u8);
|
||||
byte_vec.push(((ftw >> 16) & 0xFF) as u8);
|
||||
byte_vec.push(((ftw >> 8) & 0xFF) as u8);
|
||||
byte_vec.push(((ftw >> 0) & 0xFF) as u8);
|
||||
}
|
||||
}
|
||||
RAMDestination::Phase => {
|
||||
for deg in vec.into_iter() {
|
||||
let pow = self.degree_to_pow(deg);
|
||||
byte_vec.push(((pow >> 8) & 0xFF) as u8);
|
||||
byte_vec.push(((pow >> 0) & 0xFF) as u8);
|
||||
byte_vec.push(0);
|
||||
byte_vec.push(0);
|
||||
}
|
||||
}
|
||||
RAMDestination::Amplitude => {
|
||||
for amp in vec.into_iter() {
|
||||
let asf = self.amplitude_to_asf(amp);
|
||||
byte_vec.push(((asf >> 8) & 0xFF) as u8);
|
||||
byte_vec.push(((asf << 2) & 0xFC) as u8);
|
||||
byte_vec.push(0);
|
||||
byte_vec.push(0);
|
||||
}
|
||||
}
|
||||
RAMDestination::Polar => {
|
||||
// Alternate phase and amplitude
|
||||
let mut phase = true;
|
||||
for pol in vec.into_iter() {
|
||||
if phase {
|
||||
let pow = self.degree_to_pow(pol);
|
||||
byte_vec.push(((pow >> 8) & 0xFF) as u8);
|
||||
byte_vec.push(((pow >> 0) & 0xFF) as u8);
|
||||
phase = false;
|
||||
} else {
|
||||
let asf = self.amplitude_to_asf(pol);
|
||||
byte_vec.push(((asf >> 8) & 0xFF) as u8);
|
||||
byte_vec.push(((asf << 2) & 0xFC) as u8);
|
||||
phase = true;
|
||||
}
|
||||
}
|
||||
if phase {
|
||||
return Err(Error::DDSRAMError);
|
||||
}
|
||||
}
|
||||
}
|
||||
let data = byte_vec.as_slice();
|
||||
self.set_ram_profile(profile, start_addr, start_addr + (((data.len()/4) - 1) as u16),
|
||||
ram_dst, no_dwell_high, zero_crossing, op_mode, playback_rate, data)
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile
|
||||
* TODO: Possibly remove redundant end_addr parameter.
|
||||
* This can be inferred by start_addr and data size.
|
||||
*/
|
||||
pub fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
||||
op_mode: RAMOperationMode, playback_rate: f64, data: &[u8]
|
||||
pub unsafe fn set_frequency_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
||||
frequency_data: &[f64]
|
||||
) -> Result<(), Error<E>> {
|
||||
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile < 7);
|
||||
assert!(profile <= 7);
|
||||
assert!(end_addr >= start_addr);
|
||||
assert!(end_addr < 1024);
|
||||
assert_eq!(data.len() as u16, (end_addr - start_addr + 1) * 4);
|
||||
assert_eq!(frequency_data.len() as u16, end_addr - start_addr + 1);
|
||||
|
||||
// Clear RAM vector, and add address byte
|
||||
RAM_VEC.clear();
|
||||
RAM_VEC.push(0x16)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
|
||||
// Convert frequency data into bytes recognized by DDS
|
||||
for freq in frequency_data.iter() {
|
||||
let ftw = self.frequency_to_ftw(*freq);
|
||||
RAM_VEC.push(((ftw >> 24) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((ftw >> 16) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((ftw >> 8) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((ftw >> 0) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
}
|
||||
|
||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Frequency,
|
||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile, wrt supplied amplitude data
|
||||
* This will setup the static RAM_VEC by converting amplitude to asf
|
||||
*/
|
||||
pub unsafe fn set_amplitude_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
||||
amplitude_data: &[f64]
|
||||
) -> Result<(), Error<E>> {
|
||||
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile <= 7);
|
||||
assert!(end_addr >= start_addr);
|
||||
assert!(end_addr < 1024);
|
||||
assert_eq!(amplitude_data.len() as u16, end_addr - start_addr + 1);
|
||||
|
||||
// Clear RAM vector, and add address byte
|
||||
RAM_VEC.clear();
|
||||
RAM_VEC.push(0x16)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
|
||||
// Convert amplitude data into bytes recognized by DDS
|
||||
for amp in amplitude_data.iter() {
|
||||
let asf = self.amplitude_to_asf(*amp);
|
||||
RAM_VEC.push(((asf >> 8) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((asf << 2) & 0xFC) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(0)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(0)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
}
|
||||
|
||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Amplitude,
|
||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile, wrt supplied phase data
|
||||
* This will setup the static RAM_VEC by converting phase to ftw
|
||||
*/
|
||||
pub unsafe fn set_phase_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
||||
phase_data: &[f64]
|
||||
) -> Result<(), Error<E>> {
|
||||
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile <= 7);
|
||||
assert!(end_addr >= start_addr);
|
||||
assert!(end_addr < 1024);
|
||||
assert_eq!(phase_data.len() as u16, end_addr - start_addr + 1);
|
||||
|
||||
// Clear RAM vector, and add address byte
|
||||
RAM_VEC.clear();
|
||||
RAM_VEC.push(0x16)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
|
||||
// Convert phase data into bytes recognized by DDS
|
||||
for deg in phase_data.iter() {
|
||||
let pow = self.degree_to_pow(*deg);
|
||||
RAM_VEC.push(((pow >> 8) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((pow >> 0) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(0)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(0)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
}
|
||||
|
||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase,
|
||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile, wrt supplied phase data
|
||||
* This will setup the static RAM_VEC by converting phase to ftw
|
||||
*/
|
||||
pub unsafe fn set_polar_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
||||
polar_data: &[(f64, f64)]
|
||||
) -> Result<(), Error<E>> {
|
||||
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile <= 7);
|
||||
assert!(end_addr >= start_addr);
|
||||
assert!(end_addr < 1024);
|
||||
assert_eq!(polar_data.len() as u16, end_addr - start_addr + 1);
|
||||
|
||||
// Clear RAM vector, and add address byte
|
||||
RAM_VEC.clear();
|
||||
RAM_VEC.push(0x16)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
|
||||
// Convert amplitude data into bytes recognized by DDS
|
||||
for (deg, amp) in polar_data.iter() {
|
||||
let pow = self.degree_to_pow(*deg);
|
||||
let asf = self.amplitude_to_asf(*amp);
|
||||
RAM_VEC.push(((pow >> 8) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((pow >> 0) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((asf >> 8) & 0xFF) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
RAM_VEC.push(((asf << 2) & 0xFC) as u8)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
}
|
||||
|
||||
self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase,
|
||||
no_dwell_high, zero_crossing, op_mode, playback_rate)
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure a RAM mode profile, w.r.t static vector (RAM_VEC)
|
||||
*/
|
||||
fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
||||
op_mode: RAMOperationMode, playback_rate: f64
|
||||
) -> Result<(), Error<E>> {
|
||||
|
||||
// Check the legality of the profile setup
|
||||
assert!(profile <= 7);
|
||||
assert!(end_addr >= start_addr);
|
||||
assert!(end_addr < 1024);
|
||||
// assert_eq! RAM_VEC.len() as u16, ((end_addr - start_addr + 1) * 4) + 1);
|
||||
|
||||
// Calculate address step rate, and check legality
|
||||
let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64;
|
||||
|
@ -595,7 +671,9 @@ where
|
|||
|
||||
// Temporarily disable RAM mode while accessing into RAM
|
||||
self.disable_ram_configuration()?;
|
||||
self.write_ram(data)?;
|
||||
unsafe {
|
||||
self.write_ram()?;
|
||||
}
|
||||
|
||||
// Properly configure start_addr and end_addr
|
||||
self.enable_ram_configuration(ram_dst)
|
||||
|
@ -622,14 +700,8 @@ where
|
|||
}
|
||||
|
||||
// Write data in RAM
|
||||
fn write_ram(&mut self, data: &[u8]) -> Result<(), Error<E>> {
|
||||
let mut vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
||||
vec.try_push(0x16)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
vec.try_extend_from_slice(data)
|
||||
.map_err(|_| Error::DDSRAMError)?;
|
||||
let mut data_slice = vec.as_mut_slice();
|
||||
self.spi.transfer(&mut data_slice)
|
||||
unsafe fn write_ram(&mut self) -> Result<(), Error<E>> {
|
||||
self.spi.transfer(&mut RAM_VEC)
|
||||
.map(|_| ())
|
||||
.map_err(Error::SPI)
|
||||
}
|
||||
|
@ -654,6 +726,17 @@ where
|
|||
}
|
||||
Ok(error_count)
|
||||
}
|
||||
|
||||
// Setter function for f_sys_clk
|
||||
// Warning: This does not setup the chip to generate this actual f_sys_clk
|
||||
pub(crate) fn set_f_sys_clk(&mut self, f_sys_clk: f64) {
|
||||
self.f_sys_clk = f_sys_clk;
|
||||
}
|
||||
|
||||
// Getter function for f_sys_clk
|
||||
pub fn get_f_sys_clk(&mut self) -> f64 {
|
||||
self.f_sys_clk
|
||||
}
|
||||
}
|
||||
|
||||
// Strong check for bytes passed to a register
|
||||
|
|
|
@ -3,7 +3,6 @@ use embedded_hal::{
|
|||
blocking::spi::Transfer,
|
||||
blocking::delay::DelayUs,
|
||||
};
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FPGAFlashError {
|
||||
|
@ -12,13 +11,15 @@ pub enum FPGAFlashError {
|
|||
ResetStatusError,
|
||||
}
|
||||
|
||||
const DATA: &'static [u8] = include_bytes!("../build/top.bin");
|
||||
|
||||
// A public method to flash iCE40 FPGA on Humpback
|
||||
pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
||||
SS: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayUs<u32>,
|
||||
DONE: InputPin>
|
||||
(mut spi: SPI, mut ss: SS, mut creset: RST, cdone: DONE, mut delay: DELAY, data: &[u8]) -> Result<(), FPGAFlashError>
|
||||
(mut spi: SPI, mut ss: SS, mut creset: RST, cdone: DONE, mut delay: DELAY) -> Result<(), FPGAFlashError>
|
||||
{
|
||||
// Data buffer setup
|
||||
let mut dummy_byte :[u8; 1] = [0x00];
|
||||
|
@ -62,7 +63,7 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
|||
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
||||
|
||||
// Send the whole image without interruption
|
||||
for byte in data.into_iter() {
|
||||
for byte in DATA.into_iter() {
|
||||
let mut single_byte_slice = [*byte];
|
||||
spi.transfer(&mut single_byte_slice)
|
||||
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||
|
@ -83,10 +84,8 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
|||
_ => return Err(FPGAFlashError::ResetStatusError),
|
||||
};
|
||||
|
||||
info!("Configuration successful!");
|
||||
// Send at least another 49 clock cycles to activate IO pins (choosing same 13 bytes)
|
||||
spi.transfer(&mut dummy_13_bytes).map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||
info!("User I/O pins activated.");
|
||||
Ok(())
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Enables ITM
|
||||
pub unsafe fn enable_itm(
|
||||
dbgmcu: &stm32h7xx_hal::stm32::DBGMCU,
|
||||
dcb: &mut cortex_m::peripheral::DCB,
|
||||
itm: &mut cortex_m::peripheral::ITM
|
||||
) {
|
||||
// ARMv7-M DEMCR: Set TRCENA. Enables DWT and ITM units
|
||||
//unsafe { *(0xE000_EDFC as *mut u32) |= 1 << 24 };
|
||||
dcb.enable_trace();
|
||||
|
||||
// Ensure debug blocks are clocked before interacting with them
|
||||
dbgmcu.cr.modify(|_, w| {
|
||||
w.d1dbgcken()
|
||||
.set_bit()
|
||||
.d3dbgcken()
|
||||
.set_bit()
|
||||
.traceclken()
|
||||
.set_bit()
|
||||
.dbgsleep_d1()
|
||||
.set_bit()
|
||||
});
|
||||
|
||||
// SWO: Unlock
|
||||
*(0x5c00_3fb0 as *mut u32) = 0xC5ACCE55;
|
||||
// SWTF: Unlock
|
||||
*(0x5c00_4fb0 as *mut u32) = 0xC5ACCE55;
|
||||
|
||||
// SWO CODR Register: Set SWO speed
|
||||
*(0x5c00_3010 as *mut _) = 200;
|
||||
|
||||
// SWO SPPR Register:
|
||||
// 1 = Manchester
|
||||
// 2 = NRZ
|
||||
*(0x5c00_30f0 as *mut _) = 2;
|
||||
|
||||
// SWTF Trace Funnel: Enable for CM7
|
||||
*(0x5c00_4000 as *mut u32) |= 1;
|
||||
|
||||
// ITM: Unlock
|
||||
itm.lar.write(0xC5ACCE55);
|
||||
// ITM Trace Enable Register: Enable lower 8 stimulus ports
|
||||
itm.ter[0].write(1);
|
||||
// ITM Trace Control Register: Enable ITM
|
||||
itm.tcr.write(
|
||||
(0b000001 << 16) | // TraceBusID
|
||||
(1 << 3) | // enable SWO output
|
||||
(1 << 0), // enable the ITM
|
||||
);
|
||||
}
|
||||
|
||||
use panic_itm as _;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use log::LevelFilter;
|
||||
|
||||
pub use cortex_m_log::log::Logger;
|
||||
|
||||
use cortex_m_log::{
|
||||
destination::Itm as ItmDest,
|
||||
printer::itm::InterruptSync,
|
||||
modes::InterruptFree,
|
||||
printer::itm::ItmSync
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref LOGGER: Logger<ItmSync<InterruptFree>> = Logger {
|
||||
level: LevelFilter::Trace,
|
||||
inner: unsafe {
|
||||
InterruptSync::new(
|
||||
ItmDest::new(cortex_m::Peripherals::steal().ITM)
|
||||
)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
cortex_m_log::log::init(&LOGGER).unwrap();
|
||||
}
|
353
src/main.rs
353
src/main.rs
|
@ -1,39 +1,71 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use log::{trace, debug, info, warn};
|
||||
|
||||
use stm32h7xx_hal::hal::digital::v2::{
|
||||
InputPin,
|
||||
OutputPin,
|
||||
};
|
||||
#![feature(str_strip)]
|
||||
use log::{ trace, debug, info, warn };
|
||||
use stm32h7xx_hal::hal::digital::v2::InputPin;
|
||||
use stm32h7xx_hal::gpio::Speed;
|
||||
use stm32h7xx_hal::{pac, prelude::*, spi};
|
||||
use stm32h7xx_hal::ethernet;
|
||||
|
||||
use smoltcp as net;
|
||||
use minimq::{
|
||||
embedded_nal::{IpAddr, Ipv4Addr, TcpStack},
|
||||
MqttClient, QoS
|
||||
};
|
||||
|
||||
use cortex_m;
|
||||
use cortex_m_rt::entry;
|
||||
use rtic::cyccnt::{Instant, U32Ext};
|
||||
|
||||
use firmware;
|
||||
use firmware::{
|
||||
attenuator::Attenuator,
|
||||
config_register::{
|
||||
ConfigRegister,
|
||||
CFGMask,
|
||||
StatusMask,
|
||||
},
|
||||
dds::{
|
||||
DDS,
|
||||
DDSCFRMask,
|
||||
},
|
||||
cpld::{
|
||||
CPLD,
|
||||
}
|
||||
use heapless::Vec;
|
||||
use heapless::consts;
|
||||
|
||||
#[macro_use]
|
||||
pub mod bitmask_macro;
|
||||
pub mod spi_slave;
|
||||
pub mod cpld;
|
||||
use crate::cpld::CPLD;
|
||||
pub mod config_register;
|
||||
pub mod attenuator;
|
||||
pub mod dds;
|
||||
pub mod nal_tcp_client;
|
||||
use crate::nal_tcp_client::{ NetStorage, NetworkStack };
|
||||
pub mod flash;
|
||||
use crate::flash::flash_ice40_fpga;
|
||||
pub mod mqtt_mux;
|
||||
use crate::mqtt_mux::MqttMux;
|
||||
pub mod urukul;
|
||||
use crate::urukul::Urukul;
|
||||
|
||||
mod logger;
|
||||
|
||||
static mut NET_STORE: NetStorage = NetStorage {
|
||||
// Placeholder for the real IP address, which is initialized at runtime.
|
||||
ip_addrs: [net::wire::IpCidr::Ipv6(
|
||||
net::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX,
|
||||
)],
|
||||
neighbor_cache: [None; 8],
|
||||
routes_cache: [None; 8],
|
||||
};
|
||||
|
||||
#[path = "../examples/util/logger.rs"]
|
||||
mod logger;
|
||||
#[link_section = ".sram3.eth"]
|
||||
static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();
|
||||
|
||||
macro_rules! add_socket {
|
||||
($sockets:ident, $tx_storage:ident, $rx_storage:ident) => {
|
||||
let mut $rx_storage = [0; 4096];
|
||||
let mut $tx_storage = [0; 4096];
|
||||
|
||||
let tcp_socket = {
|
||||
let tx_buffer = net::socket::TcpSocketBuffer::new(&mut $tx_storage[..]);
|
||||
let rx_buffer = net::socket::TcpSocketBuffer::new(&mut $rx_storage[..]);
|
||||
|
||||
net::socket::TcpSocket::new(tx_buffer, rx_buffer)
|
||||
};
|
||||
|
||||
let _handle = $sockets.add(tcp_socket);
|
||||
};
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
|
@ -41,6 +73,16 @@ fn main() -> ! {
|
|||
let mut cp = cortex_m::Peripherals::take().unwrap();
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
unsafe {
|
||||
logger::enable_itm(&dp.DBGMCU, &mut cp.DCB, &mut cp.ITM);
|
||||
}
|
||||
logger::init();
|
||||
|
||||
// Enable SRAM3 for the descriptor ring.
|
||||
dp.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit());
|
||||
// // Reset RCC clock
|
||||
// dp.RCC.rsr.write(|w| w.rmvf().set_bit());
|
||||
|
||||
let pwr = dp.PWR.constrain();
|
||||
let vos = pwr.freeze();
|
||||
|
||||
|
@ -48,16 +90,17 @@ fn main() -> ! {
|
|||
let ccdr = rcc
|
||||
.use_hse(8.mhz())
|
||||
.sys_ck(400.mhz())
|
||||
.hclk(200.mhz())
|
||||
.pll1_q_ck(48.mhz())
|
||||
.pll1_r_ck(400.mhz())
|
||||
.freeze(vos, &dp.SYSCFG);
|
||||
|
||||
unsafe {
|
||||
logger::enable_itm(&dp.DBGMCU, &mut cp.DCB, &mut cp.ITM);
|
||||
}
|
||||
logger::init();
|
||||
let delay = cp.SYST.delay(ccdr.clocks);
|
||||
|
||||
let mut delay = cp.SYST.delay(ccdr.clocks);
|
||||
cp.SCB.invalidate_icache();
|
||||
cp.SCB.enable_icache();
|
||||
|
||||
cp.DWT.enable_cycle_counter();
|
||||
|
||||
let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA);
|
||||
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
||||
|
@ -65,29 +108,91 @@ fn main() -> ! {
|
|||
let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD);
|
||||
let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE);
|
||||
let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF);
|
||||
let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG);
|
||||
|
||||
// Setup CDONE for checking
|
||||
// Note: ITM doesn't work beyond this, due to a pin conflict between:
|
||||
// - FPGA_SPI: SCK (af5)
|
||||
// - ST_LINK SWO (af0)
|
||||
// Both demands PB3
|
||||
trace!("Flashing configuration bitstream to iCE40 HX8K on Humpback.");
|
||||
|
||||
// Using SPI_1 alternate functions (af5)
|
||||
let fpga_sck = gpiob.pb3.into_alternate_af5();
|
||||
let fpga_sdo = gpiob.pb4.into_alternate_af5();
|
||||
let fpga_sdi = gpiob.pb5.into_alternate_af5();
|
||||
|
||||
// Setup SPI_SS_B and CRESET_B
|
||||
let fpga_ss = gpioa.pa4.into_push_pull_output();
|
||||
let fpga_creset = gpiof.pf3.into_open_drain_output();
|
||||
|
||||
// Setup CDONE
|
||||
let fpga_cdone = gpiod.pd15.into_pull_up_input();
|
||||
|
||||
match fpga_cdone.is_high() {
|
||||
Ok(true) => info!("FPGA is ready."),
|
||||
Ok(_) => info!("FPGA is in reset state."),
|
||||
Err(_) => info!("Error: Cannot read C_DONE"),
|
||||
};
|
||||
// Setup SPI interface
|
||||
let fpga_cfg_spi = dp.SPI1.spi(
|
||||
(fpga_sck, fpga_sdo, fpga_sdi),
|
||||
spi::MODE_3,
|
||||
12.mhz(),
|
||||
ccdr.peripheral.SPI1,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
|
||||
flash_ice40_fpga(fpga_cfg_spi, fpga_ss, fpga_creset, fpga_cdone, delay).unwrap();
|
||||
|
||||
// Configure ethernet IO
|
||||
{
|
||||
let _rmii_refclk = gpioa.pa1.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_mdio = gpioa.pa2.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_mdc = gpioc.pc1.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_crs_dv = gpioa.pa7.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_rxd0 = gpioc.pc4.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_rxd1 = gpioc.pc5.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_tx_en = gpiog.pg11.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_txd0 = gpiog.pg13.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
let _rmii_txd1 = gpiob.pb13.into_alternate_af11().set_speed(Speed::VeryHigh);
|
||||
}
|
||||
|
||||
// Configure ethernet
|
||||
let mac_addr = net::wire::EthernetAddress([0xAC, 0x6F, 0x7A, 0xDE, 0xD6, 0xC8]);
|
||||
let (eth_dma, mut eth_mac) = unsafe {
|
||||
ethernet::new_unchecked(
|
||||
dp.ETHERNET_MAC,
|
||||
dp.ETHERNET_MTL,
|
||||
dp.ETHERNET_DMA,
|
||||
&mut DES_RING,
|
||||
mac_addr.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { ethernet::enable_interrupt() }
|
||||
|
||||
let store = unsafe { &mut NET_STORE };
|
||||
|
||||
store.ip_addrs[0] = net::wire::IpCidr::new(net::wire::IpAddress::v4(192, 168, 1, 200), 24);
|
||||
|
||||
let neighbor_cache = net::iface::NeighborCache::new(&mut store.neighbor_cache[..]);
|
||||
|
||||
let mut routes = net::iface::Routes::new(&mut store.routes_cache[..]);
|
||||
let default_v4_gw = net::wire::Ipv4Address::new(192, 168, 1, 1);
|
||||
routes.add_default_ipv4_route(default_v4_gw).unwrap();
|
||||
|
||||
let mut net_interface = net::iface::EthernetInterfaceBuilder::new(eth_dma)
|
||||
.ethernet_addr(mac_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(&mut store.ip_addrs[..])
|
||||
.routes(routes)
|
||||
.finalize();
|
||||
|
||||
/*
|
||||
* Using SPI1, AF5
|
||||
* SCLK -> PA5
|
||||
* MOSI -> PB5
|
||||
* MISO -> PA6
|
||||
* Using SPI6
|
||||
* SCLK -> PA5 (af8)
|
||||
* MOSI -> PG14 (af5)
|
||||
* MISO -> PA6 (af8)
|
||||
* CS -> 0: PB12, 1: PA15, 2: PC7
|
||||
*/
|
||||
|
||||
let sclk = gpioa.pa5.into_alternate_af5();
|
||||
let mosi = gpiob.pb5.into_alternate_af5();
|
||||
let miso = gpioa.pa6.into_alternate_af5();
|
||||
|
||||
|
||||
let sclk = gpioa.pa5.into_alternate_af8().set_speed(Speed::VeryHigh);
|
||||
let mosi = gpiog.pg14.into_alternate_af5().set_speed(Speed::VeryHigh);
|
||||
let miso = gpioa.pa6.into_alternate_af8().set_speed(Speed::VeryHigh);
|
||||
let (cs0, cs1, cs2) = (
|
||||
gpiob.pb12.into_push_pull_output(),
|
||||
gpioa.pa15.into_push_pull_output(),
|
||||
|
@ -99,112 +204,94 @@ fn main() -> ! {
|
|||
*/
|
||||
let io_update = gpiob.pb15.into_push_pull_output();
|
||||
|
||||
let spi = dp.SPI1.spi(
|
||||
let spi = dp.SPI6.spi(
|
||||
(sclk, miso, mosi),
|
||||
spi::MODE_0,
|
||||
3.mhz(),
|
||||
ccdr.peripheral.SPI1,
|
||||
10.mhz(),
|
||||
ccdr.peripheral.SPI6,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
|
||||
let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update);
|
||||
let parts = switch.split();
|
||||
|
||||
let mut config = ConfigRegister::new(parts.spi1);
|
||||
let mut att = Attenuator::new(parts.spi2);
|
||||
let mut dds0 = DDS::new(parts.spi4, 25_000_000.0);
|
||||
let mut urukul = Urukul::new(
|
||||
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7
|
||||
);
|
||||
|
||||
// Reset all DDS, set CLK_SEL to 0
|
||||
config.set_configurations(&mut [
|
||||
(CFGMask::RST, 1),
|
||||
(CFGMask::IO_RST, 1),
|
||||
(CFGMask::IO_UPDATE, 0)
|
||||
]).unwrap();
|
||||
urukul.reset().unwrap();
|
||||
// info!("Test value: {}", urukul.test().unwrap());
|
||||
|
||||
config.set_configurations(&mut [
|
||||
(CFGMask::IO_RST, 0),
|
||||
(CFGMask::RST, 0),
|
||||
(CFGMask::RF_SW, 13),
|
||||
(CFGMask::DIV, 3)
|
||||
]).unwrap();
|
||||
let mut mqtt_mux = MqttMux::new(urukul);
|
||||
|
||||
dds0.init().unwrap();
|
||||
// Time unit in ms
|
||||
let mut time: u32 = 0;
|
||||
|
||||
dds0.set_configurations(&mut [
|
||||
(DDSCFRMask::PDCLK_ENABLE, 0),
|
||||
(DDSCFRMask::READ_EFFECTIVE_FTW, 1),
|
||||
]).unwrap();
|
||||
// Cycle counter for 1 ms
|
||||
// This effectively provides a conversion from rtic unit to ms
|
||||
let mut next_ms = Instant::now();
|
||||
next_ms += 400_000.cycles();
|
||||
|
||||
dds0.set_sys_clk_frequency(1_000_000_000.0).unwrap();
|
||||
let mut socket_set_entries: [_; 8] = Default::default();
|
||||
let mut sockets = net::socket::SocketSet::new(&mut socket_set_entries[..]);
|
||||
add_socket!(sockets, rx_storage, tx_storage);
|
||||
|
||||
// Attenuator
|
||||
att.set_attenuation([
|
||||
5.0, 31.5, 24.0, 0.0
|
||||
]).unwrap();
|
||||
let tcp_stack = NetworkStack::new(&mut net_interface, sockets);
|
||||
|
||||
dds0.set_single_tone_profile(1, 10_000_000.0, 0.0, 0.5).unwrap();
|
||||
config.set_configurations(&mut [
|
||||
(CFGMask::PROFILE, 1),
|
||||
]).unwrap();
|
||||
// Case dealt: Ethernet connection break down, neither side has timeout
|
||||
// Limitation: Timeout inequality will cause TCP socket state to desync
|
||||
// Probably fixed in latest smoltcp commit
|
||||
let mut client = MqttClient::<consts::U256, _>::new(
|
||||
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)),
|
||||
"Urukul",
|
||||
tcp_stack,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// // Setup RAM configuration
|
||||
// dds0.set_configurations(&mut [
|
||||
// (DDSCFRMask::RAM_ENABLE, 1),
|
||||
// (DDSCFRMask::RAM_PLAYBACK_DST, 2),
|
||||
// ]).unwrap();
|
||||
let mut tick = false;
|
||||
let mut has_subscribed = false;
|
||||
|
||||
// // Configure RAM profile 0
|
||||
// dds0.write_register(0x0E, &mut [
|
||||
// 0x00, // Open
|
||||
// 0x09, 0xC4, // Address step rate (2500)
|
||||
// 0xFF, 0xC0, // End at address 1023
|
||||
// 0x00, 0x00, // Start at address 0
|
||||
// 0x04, // Recirculate mode
|
||||
// ]).unwrap();
|
||||
loop {
|
||||
// Update time accumulator in ms
|
||||
// Tick once every ms
|
||||
if Instant::now() > next_ms {
|
||||
tick = true;
|
||||
time += 1;
|
||||
next_ms += 400_000.cycles();
|
||||
}
|
||||
|
||||
// debug!("{:#X?}", dds0.read_register(0x0E, &mut[
|
||||
// 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00,
|
||||
// ]).unwrap());
|
||||
// eth Poll if necessary
|
||||
// Do not poll if eth link is down
|
||||
if tick && client.network_stack.update_delay(time) == 0 && eth_mac.phy_poll_link() {
|
||||
client.network_stack.update(time);
|
||||
}
|
||||
|
||||
// // Choose profile 0
|
||||
// config.set_configurations(&mut [
|
||||
// (CFGMask::PROFILE, 0),
|
||||
// ]).unwrap();
|
||||
// Process MQTT messages about Urukul/Control
|
||||
let connection = client
|
||||
.poll(|_client, topic, message, _properties| {
|
||||
// info!("On {:?}, received: {:?}", topic, message);
|
||||
// Why is topic a string while message is a slice?
|
||||
mqtt_mux.process_mqtt(topic, message).is_ok();
|
||||
}).is_ok();
|
||||
|
||||
// // Set RAM to be amplitudes, disable RAM momentarily
|
||||
// dds0.set_configurations(&mut [
|
||||
// (DDSCFRMask::RAM_PLAYBACK_DST, 0),
|
||||
// (DDSCFRMask::RAM_ENABLE, 0),
|
||||
// ]).unwrap();
|
||||
if connection && !has_subscribed && tick {
|
||||
match client.subscribe("Urukul/Control/#", &[]) {
|
||||
Ok(()) => has_subscribed = true,
|
||||
Err(minimq::Error::NotReady) => {},
|
||||
_e => {},
|
||||
};
|
||||
}
|
||||
|
||||
// let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1];
|
||||
// ram_data[0] = 0x16;
|
||||
// for index in 0..1024 {
|
||||
// if index % 2 == 1 {
|
||||
// ram_data[(index * 4) + 1] = 0x3F;
|
||||
// ram_data[(index * 4) + 2] = 0xFF;
|
||||
// } else {
|
||||
// ram_data[(index * 4) + 1] = 0x00;
|
||||
// ram_data[(index * 4) + 2] = 0x00;
|
||||
// }
|
||||
// // ram_data[(index * 4) + 1] = ((index >> 2) & 0xFF) as u8;
|
||||
// // ram_data[(index * 4) + 2] = ((index & 0x03) << 6) as u8;
|
||||
// }
|
||||
// dds0.transfer(&mut ram_data).unwrap();
|
||||
if connection && tick && (time % 3000) == 0 {
|
||||
client.publish("Urukul/Channel1/Switch", mqtt_mux
|
||||
.get_switch_status_message(1)
|
||||
.unwrap()
|
||||
.as_bytes(), QoS::AtMostOnce, &[])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// config.set_configurations(&mut [
|
||||
// (CFGMask::PROFILE, 1),
|
||||
// ]).unwrap();
|
||||
|
||||
// config.set_configurations(&mut [
|
||||
// (CFGMask::PROFILE, 0),
|
||||
// ]).unwrap();
|
||||
|
||||
// dds0.set_configurations(&mut [
|
||||
// (DDSCFRMask::RAM_ENABLE, 1),
|
||||
// ]).unwrap();
|
||||
|
||||
loop {}
|
||||
// Reset tick flag
|
||||
tick = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
201
src/mqtt_mux.rs
201
src/mqtt_mux.rs
|
@ -1,24 +1,18 @@
|
|||
use log::info;
|
||||
use nom::IResult;
|
||||
use nom::combinator::{value, map, map_res, not, opt, all_consuming};
|
||||
use nom::sequence::{terminated, preceded, pair, delimited, tuple};
|
||||
use nom::bytes::complete::{take, tag, tag_no_case, take_while};
|
||||
use nom::combinator::{value, map, map_res, opt, all_consuming};
|
||||
use nom::sequence::{terminated, preceded, pair};
|
||||
use nom::bytes::complete::{tag, tag_no_case, take_while};
|
||||
use nom::character::complete::digit1;
|
||||
use nom::character::is_space;
|
||||
use nom::branch::alt;
|
||||
use nom::branch::{permutation, alt};
|
||||
use nom::number::complete::{float, double};
|
||||
|
||||
use uom::si::f64::Frequency;
|
||||
use uom::si::frequency::{hertz, kilohertz, megahertz, gigahertz};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
|
||||
use embedded_hal::blocking::spi::Transfer;
|
||||
use core::convert::TryInto;
|
||||
use crate::ClockSource as UrukulClockSource;
|
||||
use crate::ClockSource::*;
|
||||
use crate::Urukul;
|
||||
use crate::Error;
|
||||
use crate::urukul::ClockSource as UrukulClockSource;
|
||||
use crate::urukul::Urukul;
|
||||
use crate::urukul::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MqttTopic {
|
||||
|
@ -284,24 +278,24 @@ impl<SPI, E> MqttMux<SPI> where SPI: Transfer<u8, Error = E> {
|
|||
MqttCommand::Profile(prof) => self.urukul.set_profile(prof),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_switch_status_message(&mut self, channel: u8) -> Result<&str, Error<E>> {
|
||||
self.urukul.get_channel_switch_status(channel.into()).map(
|
||||
|stat| if stat {
|
||||
"on"
|
||||
} else {
|
||||
"off"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Topic separator parser
|
||||
fn topic_separator<'a>(topic: &'a str) -> IResult<&'a str, ()> {
|
||||
value((), tag("/"))(topic)
|
||||
}
|
||||
|
||||
// Message separator parser
|
||||
// Read message parameter separator (optional comma and whitespace)
|
||||
fn message_separator(message: &[u8]) -> IResult<&[u8], ()> {
|
||||
value(
|
||||
(),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("/"),
|
||||
whitespace
|
||||
)
|
||||
)
|
||||
preceded(
|
||||
opt(
|
||||
tag(",")
|
||||
),
|
||||
whitespace
|
||||
)(message)
|
||||
}
|
||||
|
||||
|
@ -401,63 +395,48 @@ fn clock_division_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
|||
}
|
||||
|
||||
// Parser for one-command master clock setup message
|
||||
// Possible improvements: Chop off redundant braces and quotes
|
||||
// Allow optional parameters/permutation of parameters
|
||||
fn clock_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||
all_consuming(
|
||||
map(
|
||||
delimited(
|
||||
tag("{"),
|
||||
tuple((
|
||||
permutation((
|
||||
preceded(
|
||||
tag_no_case("source:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"source\":"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
alt((
|
||||
value(UrukulClockSource::OSC, tag_no_case("OSC")),
|
||||
value(UrukulClockSource::MMCX, tag_no_case("MMCX")),
|
||||
value(UrukulClockSource::SMA, tag_no_case("SMA"))
|
||||
)),
|
||||
tag(",")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"frequency\":"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
read_frequency,
|
||||
tag(",")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"division\":"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
map_res(
|
||||
digit1,
|
||||
|div: &[u8]| u8::from_str_radix(core::str::from_utf8(div).unwrap(), 10)
|
||||
),
|
||||
whitespace
|
||||
)
|
||||
)
|
||||
terminated(
|
||||
alt((
|
||||
value(UrukulClockSource::OSC, tag_no_case("OSC")),
|
||||
value(UrukulClockSource::MMCX, tag_no_case("MMCX")),
|
||||
value(UrukulClockSource::SMA, tag_no_case("SMA"))
|
||||
)),
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
)),
|
||||
tag("}")
|
||||
),
|
||||
),
|
||||
preceded(
|
||||
tag_no_case("frequency:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
read_frequency,
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
tag_no_case("division:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
map_res(
|
||||
digit1,
|
||||
|div: &[u8]| u8::from_str_radix(core::str::from_utf8(div).unwrap(), 10)
|
||||
),
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
)
|
||||
)),
|
||||
|(src, freq, div): (UrukulClockSource, f64, u8)| MqttCommand::Clock(src, freq, div)
|
||||
)
|
||||
)(message)
|
||||
|
@ -512,68 +491,50 @@ fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult
|
|||
}
|
||||
|
||||
// Parser for one-command singletone profile Command
|
||||
// Using JSON like command structure
|
||||
// Possible improvements: Chop off redundant braces and quotes
|
||||
// Allow optional parameters/permutation of parameters
|
||||
fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||
all_consuming(
|
||||
map(
|
||||
tuple((
|
||||
permutation((
|
||||
preceded(
|
||||
tag("{"),
|
||||
tag_no_case("frequency:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"frequency\":"),
|
||||
preceded(
|
||||
whitespace,
|
||||
read_frequency
|
||||
)
|
||||
terminated(
|
||||
read_frequency,
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
tag(","),
|
||||
tag_no_case("phase:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"amplitude\":"),
|
||||
terminated(
|
||||
double,
|
||||
preceded(
|
||||
whitespace,
|
||||
double
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
tag(","),
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("\"phase\":"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
double,
|
||||
opt(
|
||||
preceded(
|
||||
opt(
|
||||
preceded(
|
||||
whitespace,
|
||||
tag_no_case("deg")
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
whitespace,
|
||||
tag("}")
|
||||
)
|
||||
whitespace,
|
||||
tag_no_case("deg")
|
||||
)
|
||||
)
|
||||
),
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
preceded(
|
||||
tag_no_case("amplitude:"),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(
|
||||
double,
|
||||
message_separator
|
||||
)
|
||||
)
|
||||
)
|
||||
)),
|
||||
|(freq, ampl, phase): (f64, f64, f64)| MqttCommand::Singletone(channel, profile, freq, phase, ampl)
|
||||
|(freq, phase, ampl): (f64, f64, f64)| MqttCommand::Singletone(channel, profile, freq, phase, ampl)
|
||||
)
|
||||
)(message)
|
||||
}
|
||||
|
|
510
src/scpi.rs
510
src/scpi.rs
|
@ -1,510 +0,0 @@
|
|||
use scpi::error::Result;
|
||||
use scpi::expression::numeric_list;
|
||||
use scpi::expression::numeric_list::NumericList;
|
||||
use scpi::format::{Arbitrary, Character};
|
||||
use scpi::prelude::*;
|
||||
use scpi::NumericValues;
|
||||
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::str;
|
||||
use scpi::{
|
||||
nquery,
|
||||
qonly,
|
||||
};
|
||||
use scpi::suffix::{Amplitude, Db};
|
||||
use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency};
|
||||
use uom::si::angle::{degree, gon, minute as aminute, radian, revolution, Angle};
|
||||
use uom::si::{f32, f64};
|
||||
|
||||
use embedded_hal::blocking::spi::Transfer;
|
||||
|
||||
use crate::{
|
||||
Urukul,
|
||||
UrukulTraits,
|
||||
Error as UrukulError,
|
||||
ClockSource,
|
||||
};
|
||||
|
||||
use log::{trace, debug, info, warn};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! recursive_scpi_tree {
|
||||
// Handle optional headers (end-node)
|
||||
([$header_name: expr] => $handler: ident) => {
|
||||
Node {
|
||||
name: str::as_bytes($header_name),
|
||||
handler: Some(&$handler{}),
|
||||
sub: &[],
|
||||
optional: true,
|
||||
}
|
||||
};
|
||||
|
||||
// Handle non-optinal header (end-node)
|
||||
($header_name: expr => $handler: ident) => {
|
||||
Node {
|
||||
name: str::as_bytes($header_name),
|
||||
handler: Some(&$handler{}),
|
||||
sub: &[],
|
||||
optional: false,
|
||||
}
|
||||
};
|
||||
|
||||
// Handle optional header with sub-commands
|
||||
([$header_name: expr] => {$($($rest: tt)=>*),*}) => {
|
||||
Node {
|
||||
name: str::as_bytes($header_name),
|
||||
handler: None,
|
||||
sub: &[
|
||||
$(
|
||||
recursive_scpi_tree!($($rest)=>*),
|
||||
)*
|
||||
],
|
||||
optional: true,
|
||||
}
|
||||
};
|
||||
|
||||
// Handle non-optional header with sub-commands
|
||||
($header_name: expr => {$($($rest: tt)=>*),*}) => {
|
||||
Node {
|
||||
name: str::as_bytes($header_name),
|
||||
handler: None,
|
||||
sub: &[
|
||||
$(
|
||||
recursive_scpi_tree!($($rest)=>*),
|
||||
)*
|
||||
],
|
||||
optional: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! scpi_root {
|
||||
($($($node: tt)=>*),*) => {
|
||||
&Node {
|
||||
name: b"ROOT",
|
||||
optional: false,
|
||||
handler: None,
|
||||
sub: &[
|
||||
// Create default IEEE488 mandated commands
|
||||
ieee488_cls!(),
|
||||
ieee488_ese!(),
|
||||
ieee488_esr!(),
|
||||
ieee488_idn!(b"manufacturer", b"model", b"serial", b"0.1.2"),
|
||||
ieee488_opc!(),
|
||||
ieee488_rst!(),
|
||||
ieee488_sre!(),
|
||||
ieee488_stb!(),
|
||||
ieee488_tst!(),
|
||||
ieee488_wai!(),
|
||||
// Create default SCPI mandated STATus subsystem
|
||||
scpi_status!(),
|
||||
// Create default SCPI mandated SYSTem subsystem
|
||||
scpi_system!(),
|
||||
//
|
||||
scpi_crate_version!(),
|
||||
$(
|
||||
recursive_scpi_tree!($($node)=>*),
|
||||
)*
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! scpi_tree {
|
||||
() => {
|
||||
scpi_root!(
|
||||
"CHANNEL0" => {
|
||||
"SWitch" => Channel0SwitchCommand,
|
||||
"ATTenuation" => Channel0AttenuationCommand,
|
||||
"SYSCLOCK" => Channel0SystemClockCommand,
|
||||
"PROFILE0" => {
|
||||
"SINGLEtone" => {
|
||||
"FREQuency" => Channel0Profile0SingletoneFrequencyCommand,
|
||||
"PHASE" => Channel0Profile0SingletonePhaseCommand,
|
||||
"AMPlitude" => Channel0Profile0SingletoneAmplitudeCommand,
|
||||
["Setup"] => Channel0Profile0SingletoneCommand
|
||||
}
|
||||
}
|
||||
},
|
||||
"CHANNEL1" => {
|
||||
"SWitch" => Channel1SwitchCommand,
|
||||
"ATTenuation" => Channel1AttenuationCommand
|
||||
},
|
||||
"CHANNEL2" => {
|
||||
"SWitch" => Channel2SwitchCommand,
|
||||
"ATTenuation" => Channel2AttenuationCommand
|
||||
},
|
||||
"CHANNEL3" => {
|
||||
"SWitch" => Channel3SwitchCommand,
|
||||
"ATTenuation" => Channel3AttenuationCommand
|
||||
},
|
||||
"CLOCK" => {
|
||||
"SOURCE" => ClockSourceCommand,
|
||||
"DIVision" => ClockDivisionCommand
|
||||
},
|
||||
"PROFILE" => ProfileCommand,
|
||||
["EXAMple"] => {
|
||||
"HELLO" => {
|
||||
"WORLD" => HelloWorldCommand
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub struct HelloWorldCommand {}
|
||||
impl<T: Device> Command<T> for HelloWorldCommand {
|
||||
qonly!();
|
||||
|
||||
fn query(
|
||||
&self,
|
||||
_context: &mut Context<T>,
|
||||
_args: &mut Tokenizer,
|
||||
response: &mut ResponseUnit,
|
||||
) -> Result<()> {
|
||||
response.data(b"Hello world" as &[u8]).finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Channel0SwitchCommand {}
|
||||
pub struct Channel1SwitchCommand {}
|
||||
pub struct Channel2SwitchCommand {}
|
||||
pub struct Channel3SwitchCommand {}
|
||||
|
||||
macro_rules! impl_channel_switch_command {
|
||||
($($channel: literal => $command_struct: ty),*) => {
|
||||
$(
|
||||
impl<T: Device + UrukulTraits> Command<T> for $command_struct {
|
||||
nquery!();
|
||||
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
let next_state: bool = args.next_data(true)?
|
||||
.map_or(
|
||||
context.device.get_channel_switch_status($channel)
|
||||
.map(|current| !current)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError)),
|
||||
|token| token.try_into()
|
||||
)?;
|
||||
context.device.set_channel_switch($channel, next_state).map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_channel_switch_command!(
|
||||
0 => Channel0SwitchCommand,
|
||||
1 => Channel1SwitchCommand,
|
||||
2 => Channel2SwitchCommand,
|
||||
3 => Channel3SwitchCommand
|
||||
);
|
||||
|
||||
pub struct ClockSourceCommand {}
|
||||
pub struct ClockDivisionCommand {}
|
||||
pub struct Channel0SystemClockCommand {}
|
||||
pub struct ProfileCommand {}
|
||||
pub struct Channel0Profile0SingletoneCommand {}
|
||||
pub struct Channel0Profile0SingletoneFrequencyCommand {}
|
||||
pub struct Channel0Profile0SingletonePhaseCommand {}
|
||||
pub struct Channel0Profile0SingletoneAmplitudeCommand {}
|
||||
|
||||
// Handle CLOCK:SOURCE command, setup the proper source for the system clock
|
||||
// Leave clock division to CLOCK:DIVision command
|
||||
impl<T:Device + UrukulTraits> Command<T> for ClockSourceCommand {
|
||||
nquery!();
|
||||
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
// next_data() fucntion call can never return CharacterProgramData, could be an oversight
|
||||
let s: &[u8] = match args.next_data(false)? {
|
||||
Some(Token::CharacterProgramData(s)) => s,
|
||||
_ => return Err(ErrorCode::IllegalParameterValue.into()),
|
||||
};
|
||||
|
||||
let s_str: &str = str::from_utf8(s)
|
||||
.map_err(|_| ErrorCode::CharacterDataError)?;
|
||||
|
||||
let frequency: f64::Frequency = args.next_data(true)?
|
||||
.map_or(Ok(f64::Frequency::new::<hertz>(0.0)), |t| {
|
||||
t.numeric(|s| match s {
|
||||
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received master clock frequency: {:?}", frequency);
|
||||
|
||||
let clock_source = match s_str {
|
||||
source if source.eq_ignore_ascii_case("OSC") => {
|
||||
// If clock source is OSC, it must be 100MHz (not configurable)
|
||||
if frequency.get::<megahertz>() != 100.0 {
|
||||
warn!("Clock selection failed! OSC must be 100 MHz");
|
||||
return Err(ErrorCode::IllegalParameterValue.into());
|
||||
}
|
||||
ClockSource::OSC
|
||||
},
|
||||
source if source.eq_ignore_ascii_case("MMCX") => {
|
||||
// TODO: Implement frequency check for MMCX
|
||||
ClockSource::MMCX
|
||||
},
|
||||
source if source.eq_ignore_ascii_case("SMA") => {
|
||||
// TODO: Implement frequency check for SMA
|
||||
ClockSource::SMA
|
||||
},
|
||||
_ => {
|
||||
warn!("Clock selection failed! Argument error!");
|
||||
return Err(ErrorCode::IllegalParameterValue.into());
|
||||
},
|
||||
};
|
||||
|
||||
trace!("Changing clock source to {:?} at {:?}", clock_source, frequency);
|
||||
context.device.set_clock_source(clock_source, frequency.get::<hertz>())
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for ClockDivisionCommand {
|
||||
nquery!();
|
||||
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
let div :f32 = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::IllegalParameterValue)),
|
||||
|token| token.try_into())?;
|
||||
trace!("Received master clock division factor: {}", div);
|
||||
if div == 1.0 || div == 2.0 || div == 4.0 {
|
||||
debug!("Set master clock division as {}", div);
|
||||
context.device.set_clock_division(div as u8)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
} else {
|
||||
Err(Error::new(ErrorCode::IllegalParameterValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for Channel0SystemClockCommand {
|
||||
nquery!();
|
||||
|
||||
// Param: <frequency>
|
||||
// The exact method of generating this frequency is auto-decided
|
||||
// The process is delegated to individual DDS chip
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
let frequency: f64::Frequency = args.next_data(true)?
|
||||
.map_or(Ok(f64::Frequency::new::<hertz>(0.0)), |t| {
|
||||
t.numeric(|s| match s {
|
||||
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received channel 0 system clock frequency: {:?}", frequency);
|
||||
|
||||
// Setup sys_clk through urukul interface
|
||||
context.device.set_channel_sys_clk(0, frequency.get::<hertz>()).map_err(|_| Error::new(ErrorCode::IllegalParameterValue))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Channel0AttenuationCommand {}
|
||||
pub struct Channel1AttenuationCommand {}
|
||||
pub struct Channel2AttenuationCommand {}
|
||||
pub struct Channel3AttenuationCommand {}
|
||||
|
||||
macro_rules! impl_channel_attenuation_command {
|
||||
($($channel: literal => $command_struct: ty),*) => {
|
||||
$(
|
||||
impl<T:Device + UrukulTraits> Command<T> for $command_struct {
|
||||
nquery!();
|
||||
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
let attenuation: f32 = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::IllegalParameterValue)),
|
||||
|token| token.try_into())?;
|
||||
trace!("Received channel {} attenuation input: {}", $channel, attenuation);
|
||||
context.device.set_channel_attenuation($channel, attenuation)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_channel_attenuation_command!(
|
||||
0 => Channel0AttenuationCommand,
|
||||
1 => Channel1AttenuationCommand,
|
||||
2 => Channel2AttenuationCommand,
|
||||
3 => Channel3AttenuationCommand
|
||||
);
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for ProfileCommand {
|
||||
nquery!();
|
||||
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
let profile :f32 = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::IllegalParameterValue)),
|
||||
|token| token.try_into())?;
|
||||
if ((profile as u8) as f32) != profile {
|
||||
return Err(Error::new(ErrorCode::IllegalParameterValue));
|
||||
}
|
||||
|
||||
trace!("Selected Profile :{}", profile);
|
||||
let profile = profile as u8;
|
||||
if profile >= 8 {
|
||||
Err(Error::new(ErrorCode::IllegalParameterValue))
|
||||
} else {
|
||||
context.device.set_profile(profile)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneCommand {
|
||||
nquery!();
|
||||
|
||||
// Params: frequency, phase, amplitude (all mandatory)
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
// Read output frequency
|
||||
let frequency: f64::Frequency = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||
t.numeric(|s| match s {
|
||||
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency);
|
||||
// Handle negative frequency
|
||||
if frequency.get::<hertz>() < 0.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
|
||||
// Read phase offset
|
||||
let phase: f64::Angle = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||
t.numeric(
|
||||
|s| match s {
|
||||
NumericValues::Default => Ok(f64::Angle::new::<degree>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase);
|
||||
// Handle out-of-bound phase offset
|
||||
if phase.get::<degree>() < 0.0 || phase.get::<degree>() >= 360.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
|
||||
// Read amplitude offset
|
||||
let amplitude: f64 = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)),
|
||||
|token| token.try_into())?;
|
||||
trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude);
|
||||
// Handle out-of-bound phase offset
|
||||
if amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
|
||||
trace!("Set up a single tone on channel 0, profile 0");
|
||||
context.device.set_channel_single_tone_profile(0, 0, frequency.get::<hertz>(), phase.get::<degree>(), amplitude)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneFrequencyCommand {
|
||||
// TODO: Implement query for publishing
|
||||
nquery!();
|
||||
|
||||
// Param: frequency
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
// Read output frequency
|
||||
let frequency: f64::Frequency = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||
t.numeric(|s| match s {
|
||||
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency);
|
||||
// Handle negative frequency
|
||||
if frequency.get::<hertz>() < 0.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
|
||||
context.device.set_channel_single_tone_profile_frequency(0, 0, frequency.get::<hertz>())
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletonePhaseCommand {
|
||||
// TODO: Implement query for publishing
|
||||
nquery!();
|
||||
|
||||
// Param: frequency
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
// Read phase offset
|
||||
let phase: f64::Angle = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||
t.numeric(
|
||||
|s| match s {
|
||||
NumericValues::Default => Ok(f64::Angle::new::<degree>(0.0)),
|
||||
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||
})
|
||||
})?;
|
||||
trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase);
|
||||
// Handle out-of-bound phase offset
|
||||
if phase.get::<degree>() < 0.0 || phase.get::<degree>() >= 360.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
context.device.set_channel_single_tone_profile_phase(0, 0, phase.get::<degree>())
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneAmplitudeCommand {
|
||||
// TODO: Implement query for publishing
|
||||
nquery!();
|
||||
|
||||
// Param: frequency
|
||||
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||
|
||||
// Read amplitude offset
|
||||
let amplitude: f64 = args.next_data(false)?
|
||||
.map_or(Err(Error::new(ErrorCode::MissingParameter)),
|
||||
|token| token.try_into())?;
|
||||
trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude);
|
||||
// Handle out-of-bound phase offset
|
||||
if amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(ErrorCode::DataOutOfRange.into());
|
||||
}
|
||||
context.device.set_channel_single_tone_profile_amplitude(0, 0, amplitude)
|
||||
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implement "Device" trait from SCPI
|
||||
* TODO: Implement mandatory commands
|
||||
*/
|
||||
impl<SPI, E> Device for Urukul<SPI>
|
||||
where
|
||||
SPI: Transfer<u8, Error = E>
|
||||
{
|
||||
fn cls(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rst(&mut self) -> Result<()> {
|
||||
match self.reset() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(Error::new(ErrorCode::HardwareError))
|
||||
}
|
||||
}
|
||||
|
||||
fn tst(&mut self) -> Result<()> {
|
||||
match self.test() {
|
||||
Ok(0) => Ok(()),
|
||||
Ok(_) => Err(Error::new(ErrorCode::SelfTestFailed)),
|
||||
Err(_) => Err(Error::new(ErrorCode::SelfTestFailed)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use embedded_hal::{
|
|||
digital::v2::OutputPin,
|
||||
};
|
||||
use crate::cpld::CPLD;
|
||||
use crate::Error;
|
||||
use crate::urukul::Error;
|
||||
|
||||
pub struct SPISlave<'a, SPI, CS0, CS1, CS2, GPIO> (
|
||||
// SPI device to be multiplexed
|
||||
|
@ -29,7 +29,7 @@ impl<'a, SPI, CS0, CS1, CS2, GPIO> Parts<'a, SPI, CS0, CS1, CS2, GPIO> {
|
|||
Parts {
|
||||
spi1: SPISlave(&cpld, 1, false),
|
||||
spi2: SPISlave(&cpld, 2, false),
|
||||
spi3: SPISlave(&cpld, 3, true),
|
||||
spi3: SPISlave(&cpld, 3, false),
|
||||
spi4: SPISlave(&cpld, 4, true),
|
||||
spi5: SPISlave(&cpld, 5, true),
|
||||
spi6: SPISlave(&cpld, 6, true),
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
use scpi::prelude::*;
|
||||
use scpi::Context;
|
||||
use scpi::error::Result;
|
||||
use log::{trace, info};
|
||||
use arrayvec::{ArrayVec};
|
||||
|
||||
pub trait MqttScpiTranslator {
|
||||
// Unwrap an MQTT publish message into SCPI compatible command
|
||||
// The command part/ MQTT message must follow SCPI standard for parameter formatting
|
||||
fn run_with_mqtt<FMT: Formatter>(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> {
|
||||
fn run_with_mqtt<FMT>(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>
|
||||
where
|
||||
FMT: Formatter,
|
||||
{
|
||||
if !topic.starts_with("Urukul/Control") {
|
||||
info!("Received a publish, but not for control! Topic: {}", topic);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// let command_topic = topic.strip_prefix("Urukul/Control")
|
||||
// .unwrap_or("");
|
||||
|
||||
// Create a fixed-size buffer to handle slice operation
|
||||
let mut buffer = ArrayVec::<[u8; 1024]>::new();
|
||||
|
||||
// // Copy MQTT topic, convert it into SCPI header format
|
||||
// for i in command_topic.chars() {
|
||||
// if i == '/' {
|
||||
// // The topic separator is colon(':') in SCPI, and slash('/') in MQTT
|
||||
// buffer.try_push(b':')
|
||||
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||
// } else {
|
||||
// buffer.try_push(i as u8)
|
||||
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Place a space bar between header and parameter
|
||||
// buffer.try_push(b' ')
|
||||
// .map_err(|_| ErrorCode::OutOfMemory)?;
|
||||
|
||||
// Copy the arguments into the buffer
|
||||
for i in args.chars() {
|
||||
buffer.try_push(i as u8)
|
||||
.map_err(|_| ErrorCode::OutOfMemory)?;
|
||||
}
|
||||
|
||||
// Pass the message to SCPI processing unit
|
||||
trace!("Translated MQTT message into SCPI. Translated command: {}",
|
||||
core::str::from_utf8(buffer.as_slice()).unwrap());
|
||||
self.run(buffer.as_slice(), response)
|
||||
}
|
||||
}
|
|
@ -1,33 +1,14 @@
|
|||
#![no_std]
|
||||
#![feature(str_strip)]
|
||||
extern crate embedded_hal;
|
||||
use embedded_hal::{
|
||||
blocking::spi::Transfer,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
pub mod bitmask_macro;
|
||||
pub mod spi_slave;
|
||||
pub mod cpld;
|
||||
|
||||
pub mod config_register;
|
||||
use crate::config_register::ConfigRegister;
|
||||
use crate::config_register::CFGMask;
|
||||
use crate::config_register::StatusMask;
|
||||
|
||||
pub mod attenuator;
|
||||
use crate::attenuator::Attenuator;
|
||||
|
||||
pub mod dds;
|
||||
use crate::dds::DDS;
|
||||
|
||||
// pub mod scpi;
|
||||
pub mod translation;
|
||||
pub mod nal_tcp_client;
|
||||
pub mod flash;
|
||||
|
||||
pub mod mqtt_mux;
|
||||
|
||||
/*
|
||||
* Enum for structuring error
|
||||
*/
|
||||
|
@ -60,6 +41,7 @@ pub enum ClockSource {
|
|||
pub struct Urukul<SPI> {
|
||||
config_register: ConfigRegister<SPI>,
|
||||
attenuator: Attenuator<SPI>,
|
||||
multi_dds: DDS<SPI>,
|
||||
dds: [DDS<SPI>; 4],
|
||||
f_master_clk: f64,
|
||||
}
|
||||
|
@ -77,7 +59,9 @@ where
|
|||
Urukul {
|
||||
config_register: ConfigRegister::new(spi1),
|
||||
attenuator: Attenuator::new(spi2),
|
||||
// Create 4 DDS instances with fixed 25MHz clock
|
||||
// Create a multi-channel DDS with predefined 25MHz clock
|
||||
multi_dds: DDS::new(spi3, 25_000_000.0),
|
||||
// Create 4 DDS instances with predefined 25MHz clock
|
||||
// Counter-intuitive to assign urukul clock before having a urukul
|
||||
dds: [
|
||||
DDS::new(spi4, 25_000_000.0),
|
||||
|
@ -85,7 +69,7 @@ where
|
|||
DDS::new(spi6, 25_000_000.0),
|
||||
DDS::new(spi7, 25_000_000.0),
|
||||
],
|
||||
// Default clock selection: OSC, fixed 100MHz speed
|
||||
// Default clock selection: OSC, predefined 100MHz speed
|
||||
f_master_clk: 100_000_000.0,
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +134,7 @@ where
|
|||
SPI: Transfer<u8, Error = E>
|
||||
{
|
||||
|
||||
fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Error<E>> {
|
||||
pub fn get_channel_switch_status(&mut self, channel: u32) -> Result<bool, Error<E>> {
|
||||
if channel < 4 {
|
||||
self.config_register.get_status(StatusMask::RF_SW).map(|val| (val & (1 << channel)) != 0)
|
||||
} else {
|
||||
|
@ -158,7 +142,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn set_channel_switch(&mut self, channel: u32, status: bool) -> Result<(), Error<E>> {
|
||||
pub fn set_channel_switch(&mut self, channel: u32, status: bool) -> Result<(), Error<E>> {
|
||||
if channel < 4 {
|
||||
let prev = u32::from(self.config_register.get_status(StatusMask::RF_SW)?);
|
||||
let next = {
|
||||
|
@ -176,7 +160,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error<E>> {
|
||||
pub fn set_clock(&mut self, source: ClockSource, frequency: f64, division: u8) -> Result<(), Error<E>> {
|
||||
// Change clock source through configuration register
|
||||
self.set_clock_source(source)?;
|
||||
|
||||
|
@ -187,7 +171,7 @@ where
|
|||
self.set_clock_division(division)
|
||||
}
|
||||
|
||||
fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Error<E>> {
|
||||
pub fn set_clock_source(&mut self, source: ClockSource) -> Result<(), Error<E>> {
|
||||
// Change clock source through configuration register
|
||||
match source {
|
||||
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
||||
|
@ -204,7 +188,7 @@ where
|
|||
}.map(|_| ())
|
||||
}
|
||||
|
||||
fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error<E>> {
|
||||
pub fn set_clock_frequency(&mut self, frequency: f64) -> Result<(), Error<E>> {
|
||||
// Update master clock frequency
|
||||
self.f_master_clk = frequency;
|
||||
|
||||
|
@ -212,7 +196,7 @@ where
|
|||
self.set_dds_ref_clk()
|
||||
}
|
||||
|
||||
fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
|
||||
pub fn set_clock_division(&mut self, division: u8) -> Result<(), Error<E>> {
|
||||
match division {
|
||||
1 => self.config_register.set_configurations(&mut [
|
||||
(CFGMask::DIV, 1),
|
||||
|
@ -231,7 +215,7 @@ where
|
|||
|
||||
fn set_dds_ref_clk(&mut self) -> Result<(), Error<E>> {
|
||||
// Calculate reference clock frequency after clock division from configuration register
|
||||
let f_ref_clk = self.f_master_clk / (self.config_register.get_configuration(CFGMask::DIV) as f64);
|
||||
let f_ref_clk = self.f_master_clk / (self.get_master_clock_division() as f64);
|
||||
|
||||
// Update all DDS chips on reference clock frequency
|
||||
for dds_channel in 0..4 {
|
||||
|
@ -240,14 +224,23 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
||||
fn get_master_clock_division(&mut self) -> u8 {
|
||||
match self.config_register.get_configuration(CFGMask::DIV) {
|
||||
0 | 3 => 4,
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
_ => panic!("Divisor out of range, when reading configuration register (CPLD)."),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Error<E>> {
|
||||
if channel >= 4 || attenuation < 0.0 || attenuation > 31.5 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
self.attenuator.set_channel_attenuation(channel, attenuation)
|
||||
}
|
||||
|
||||
fn set_profile(&mut self, profile: u8) -> Result<(), Error<E>> {
|
||||
pub fn set_profile(&mut self, profile: u8) -> Result<(), Error<E>> {
|
||||
if profile >= 8 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
|
@ -256,7 +249,7 @@ where
|
|||
]).map(|_| ())
|
||||
}
|
||||
|
||||
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||
pub fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||
if channel >= 4 || profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(Error::ParameterError);
|
||||
|
@ -264,28 +257,82 @@ where
|
|||
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
||||
}
|
||||
|
||||
fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
|
||||
pub fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Error<E>> {
|
||||
if channel >= 4 || profile >= 8 || frequency < 0.0 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
|
||||
}
|
||||
|
||||
fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> {
|
||||
pub fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Error<E>> {
|
||||
if channel >= 4 || profile >= 8 || phase >= 360.0 || phase < 0.0 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
|
||||
}
|
||||
|
||||
fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> {
|
||||
pub fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Error<E>> {
|
||||
if channel >= 4 || profile >= 8 || amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
||||
}
|
||||
|
||||
fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Error<E>> {
|
||||
self.dds[usize::from(channel)].set_sys_clk_frequency(f_sys_clk)
|
||||
pub fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Error<E>> {
|
||||
self.dds[usize::from(channel)].set_sys_clk_frequency(f_sys_clk).map(|_| ())
|
||||
}
|
||||
|
||||
// Multi-dds channel functions
|
||||
// Do not allow reading of DDS registers
|
||||
// Make sure only 1 SPI transaction is compelted per function call
|
||||
|
||||
// Setup NU_MASK in configuration register
|
||||
// This selects the DDS channels that will be covered by multi_channel DDS (spi3)
|
||||
// Note: If a channel is masked, io_update must be completed through configuration register (IO_UPDATE bit-field)
|
||||
// Implication: Deselect such channel if individual communication is needed.
|
||||
pub fn set_multi_channel_coverage(&mut self, channel: u8) -> Result<(), Error<E>> {
|
||||
self.config_register.set_configurations(&mut [
|
||||
(CFGMask::MASK_NU, channel.into())
|
||||
]).map(|_| ())
|
||||
}
|
||||
|
||||
// Difference from individual single tone setup function:
|
||||
// - Remove the need of passing channel
|
||||
// All selected channels must share the same f_sys_clk
|
||||
pub fn set_multi_channel_single_tone_profile(&mut self, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Error<E>> {
|
||||
if profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||
return Err(Error::ParameterError);
|
||||
}
|
||||
// Check f_sys_clk of all selected channels
|
||||
let selected_channels = self.config_register.get_configuration(CFGMask::MASK_NU);
|
||||
let mut found_a_selected_channel = false;
|
||||
let mut reported_f_sys_clk: f64 = 0.0;
|
||||
for channel_bit in 0..4 {
|
||||
if (selected_channels & (1 << (channel_bit as u8))) != 0 {
|
||||
if !found_a_selected_channel {
|
||||
found_a_selected_channel = true;
|
||||
reported_f_sys_clk = self.dds[channel_bit].get_f_sys_clk();
|
||||
} else if reported_f_sys_clk != self.dds[channel_bit].get_f_sys_clk() {
|
||||
return Err(Error::DDSError);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.multi_dds.set_sys_clk_frequency(reported_f_sys_clk);
|
||||
self.multi_dds.set_single_tone_profile(profile, frequency, phase, amplitude)?;
|
||||
self.invoke_io_update()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Generate a pulse for io_update bit in configuration register
|
||||
// This acts like io_update in CPLD struct, but for multi-dds channel
|
||||
fn invoke_io_update(&mut self) -> Result<(), Error<E>> {
|
||||
self.config_register.set_configurations(&mut [
|
||||
(CFGMask::IO_UPDATE, 1)
|
||||
])?;
|
||||
self.config_register.set_configurations(&mut [
|
||||
(CFGMask::IO_UPDATE, 0)
|
||||
]).map(|_| ())
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue