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",
|
"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]]
|
[[package]]
|
||||||
name = "as-slice"
|
name = "as-slice"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -65,35 +53,12 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
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]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
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]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -103,15 +68,6 @@ dependencies = [
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.0.59"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381"
|
|
||||||
dependencies = [
|
|
||||||
"jobserver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
@ -228,31 +184,6 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -281,19 +212,6 @@ dependencies = [
|
||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "hash32"
|
name = "hash32"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -311,9 +229,9 @@ checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heapless"
|
name = "heapless"
|
||||||
version = "0.5.5"
|
version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0"
|
checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as-slice",
|
"as-slice",
|
||||||
"generic-array 0.13.2",
|
"generic-array 0.13.2",
|
||||||
|
@ -322,14 +240,25 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "humpback-dds"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matches",
|
"cortex-m",
|
||||||
"unicode-bidi",
|
"cortex-m-log",
|
||||||
"unicode-normalization",
|
"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]]
|
[[package]]
|
||||||
|
@ -342,15 +271,6 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jobserver"
|
|
||||||
version = "0.1.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -360,52 +280,6 @@ dependencies = [
|
||||||
"spin",
|
"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]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -421,12 +295,6 @@ version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
|
checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matches"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
|
@ -476,36 +344,6 @@ dependencies = [
|
||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "panic-halt"
|
name = "panic-halt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -540,18 +378,6 @@ dependencies = [
|
||||||
"proc-macro-hack",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
|
@ -605,31 +431,7 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver 0.9.0",
|
"semver",
|
||||||
]
|
|
||||||
|
|
||||||
[[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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -637,16 +439,6 @@ name = "semver"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
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 = [
|
dependencies = [
|
||||||
"semver-parser",
|
"semver-parser",
|
||||||
]
|
]
|
||||||
|
@ -657,31 +449,10 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
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]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/smoltcp-rs/smoltcp.git#bdfa44270e9c59b3095b555cdf14601f7dc27794"
|
||||||
checksum = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -742,95 +513,24 @@ dependencies = [
|
||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
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]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
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]]
|
[[package]]
|
||||||
name = "vcell"
|
name = "vcell"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
|
47
Cargo.toml
47
Cargo.toml
|
@ -2,7 +2,7 @@
|
||||||
authors = ["occheung"]
|
authors = ["occheung"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
name = "firmware"
|
name = "humpback-dds"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -16,8 +16,7 @@ nb = "1.0.0"
|
||||||
|
|
||||||
embedded-nal = "0.1.0"
|
embedded-nal = "0.1.0"
|
||||||
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" }
|
||||||
heapless = "0.5.5"
|
heapless = "0.5.6"
|
||||||
arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] }
|
|
||||||
nom = { version = "5.1.2", default-features = false, features = [] }
|
nom = { version = "5.1.2", default-features = false, features = [] }
|
||||||
|
|
||||||
# Logging and Panicking
|
# Logging and Panicking
|
||||||
|
@ -27,46 +26,8 @@ cortex-m-log = { version = "0.6.2", features = [ "itm", "log-integration" ] }
|
||||||
log = {version = "0.4.11"}
|
log = {version = "0.4.11"}
|
||||||
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
||||||
|
|
||||||
[dependencies.scpi]
|
[patch.crates-io]
|
||||||
git = "https://github.com/occheung/scpi-rs"
|
smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git" }
|
||||||
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
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1 # better optimizations
|
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);
|
client.network_stack.update(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process MQTT messages about Urukul/Control
|
||||||
let connection = client
|
let connection = client
|
||||||
.poll(|_client, topic, message, _properties| {
|
.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?
|
// Why is topic a string while message is a slice?
|
||||||
mqtt_mux.process_mqtt(topic, message);
|
mqtt_mux.process_mqtt(topic, message);
|
||||||
}).is_ok();
|
}).is_ok();
|
||||||
|
|
|
@ -10,6 +10,9 @@ from migen.genlib.io import *
|
||||||
class UrukulConnector(Module):
|
class UrukulConnector(Module):
|
||||||
def __init__(self, platform):
|
def __init__(self, platform):
|
||||||
# Include extension
|
# Include extension
|
||||||
|
spi_mosi = [
|
||||||
|
("spi_mosi", 0, Pins("B16"), IOStandard("LVCMOS33"))
|
||||||
|
]
|
||||||
spi_cs = [
|
spi_cs = [
|
||||||
("spi_cs", 0, Pins("B13 B14 B15"), IOStandard("LVCMOS33"))
|
("spi_cs", 0, Pins("B13 B14 B15"), IOStandard("LVCMOS33"))
|
||||||
]
|
]
|
||||||
|
@ -20,6 +23,7 @@ class UrukulConnector(Module):
|
||||||
# Add extensions
|
# Add extensions
|
||||||
platform.add_extension(spi_cs)
|
platform.add_extension(spi_cs)
|
||||||
platform.add_extension(io_update)
|
platform.add_extension(io_update)
|
||||||
|
platform.add_extension(spi_mosi)
|
||||||
|
|
||||||
# Request EEM I/O & SPI
|
# Request EEM I/O & SPI
|
||||||
eem0 = [
|
eem0 = [
|
||||||
|
@ -34,12 +38,13 @@ class UrukulConnector(Module):
|
||||||
platform.request("eem0", 6)
|
platform.request("eem0", 6)
|
||||||
]
|
]
|
||||||
spi = platform.request("spi")
|
spi = platform.request("spi")
|
||||||
|
spi_mosi = platform.request("spi_mosi")
|
||||||
spi_cs = platform.request("spi_cs")
|
spi_cs = platform.request("spi_cs")
|
||||||
led = platform.request("user_led")
|
led = platform.request("user_led")
|
||||||
io_update = platform.request("io_update")
|
io_update = platform.request("io_update")
|
||||||
|
|
||||||
assert len(spi.clk) == 1
|
assert len(spi.clk) == 1
|
||||||
assert len(spi.mosi) == 1
|
assert len(spi_mosi) == 1
|
||||||
assert len(spi.miso) == 1
|
assert len(spi.miso) == 1
|
||||||
assert len(spi_cs) == 3
|
assert len(spi_cs) == 3
|
||||||
assert len(io_update) == 1
|
assert len(io_update) == 1
|
||||||
|
@ -61,8 +66,8 @@ class UrukulConnector(Module):
|
||||||
eem0[0].p.eq(spi.clk),
|
eem0[0].p.eq(spi.clk),
|
||||||
eem0[0].n.eq(~spi.clk),
|
eem0[0].n.eq(~spi.clk),
|
||||||
|
|
||||||
eem0[1].p.eq(spi.mosi),
|
eem0[1].p.eq(spi_mosi),
|
||||||
eem0[1].n.eq(~spi.mosi),
|
eem0[1].n.eq(~spi_mosi),
|
||||||
|
|
||||||
spi.miso.eq(~self.miso_n),
|
spi.miso.eq(~self.miso_n),
|
||||||
|
|
||||||
|
|
14
shell.nix
14
shell.nix
|
@ -10,7 +10,7 @@ let
|
||||||
|
|
||||||
runOpenOcd = writeShellScriptBin "run-openocd" ''
|
runOpenOcd = writeShellScriptBin "run-openocd" ''
|
||||||
openocd \
|
openocd \
|
||||||
-f board/st_nucleo_h743zi.cfg \
|
-f openocd/openocd.cfg \
|
||||||
-c init &
|
-c init &
|
||||||
sleep 1
|
sleep 1
|
||||||
'';
|
'';
|
||||||
|
@ -49,12 +49,6 @@ let
|
||||||
&& flash-fpga-config
|
&& 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" ''
|
resetFlash = writeShellScriptBin "reset-flash" ''
|
||||||
gdb -batch -x gdb_config/reset.gdb
|
gdb -batch -x gdb_config/reset.gdb
|
||||||
echo "Reset is complete, please reset the openocd server."
|
echo "Reset is complete, please reset the openocd server."
|
||||||
|
@ -64,6 +58,10 @@ let
|
||||||
openocd -f openocd/openocd.cfg -f openocd/$1.cfg
|
openocd -f openocd/openocd.cfg -f openocd/$1.cfg
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
publishMqtt = writeShellScriptBin "publish-mqtt" ''
|
||||||
|
mosquitto_pub -h localhost -t $1 -m "$2" -d
|
||||||
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
name = "nix-shell";
|
name = "nix-shell";
|
||||||
|
@ -85,8 +83,8 @@ in
|
||||||
compileMigenScript
|
compileMigenScript
|
||||||
flashFPGAConfig
|
flashFPGAConfig
|
||||||
configureFPGA
|
configureFPGA
|
||||||
verifyFPGAConfig
|
|
||||||
resetFlash
|
resetFlash
|
||||||
openocdFlash
|
openocdFlash
|
||||||
|
publishMqtt
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use core::assert;
|
use core::assert;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::urukul::Error;
|
||||||
|
|
||||||
pub struct Attenuator<SPI> {
|
pub struct Attenuator<SPI> {
|
||||||
spi: SPI,
|
spi: SPI,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use crate::Error;
|
use crate::urukul::Error;
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
|
|
||||||
// Bitmasks for CFG
|
// Bitmasks for CFG
|
||||||
|
@ -75,7 +75,6 @@ where
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return selected configuration field
|
* Return selected configuration field
|
||||||
* TODO: Return result type instead for error checking
|
|
||||||
*/
|
*/
|
||||||
pub fn get_configuration(&mut self, config_type: CFGMask) -> u8 {
|
pub fn get_configuration(&mut self, config_type: CFGMask) -> u8 {
|
||||||
config_type.get_filtered_content(self.data) as 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 crate::spi_slave::Parts;
|
||||||
|
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
|
|
269
src/dds.rs
269
src/dds.rs
|
@ -1,8 +1,9 @@
|
||||||
use embedded_hal::blocking::spi::Transfer;
|
use embedded_hal::blocking::spi::Transfer;
|
||||||
use crate::Error;
|
use crate::urukul::Error;
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use arrayvec::ArrayVec;
|
use heapless::Vec;
|
||||||
|
use heapless::consts::*;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
* Bitmask for all configurations (Order: CFR3, CFR2, CFR1)
|
||||||
|
@ -64,6 +65,8 @@ construct_bitmask!(DDSCFRMask; u32;
|
||||||
const WRITE_MASK :u8 = 0x00;
|
const WRITE_MASK :u8 = 0x00;
|
||||||
const READ_MASK :u8 = 0x80;
|
const READ_MASK :u8 = 0x80;
|
||||||
|
|
||||||
|
static mut RAM_VEC: Vec<u8, U8192> = Vec(heapless::i::Vec::new());
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum RAMDestination {
|
pub enum RAMDestination {
|
||||||
Frequency = 0,
|
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,
|
pub unsafe fn set_frequency_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16,
|
||||||
ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool,
|
no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64,
|
||||||
op_mode: RAMOperationMode, playback_rate: f64, f: F) -> Result<(), Error<E>>
|
frequency_data: &[f64]
|
||||||
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]
|
|
||||||
) -> Result<(), Error<E>> {
|
) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
// Check the legality of the profile setup
|
// Check the legality of the profile setup
|
||||||
assert!(profile < 7);
|
assert!(profile <= 7);
|
||||||
assert!(end_addr >= start_addr);
|
assert!(end_addr >= start_addr);
|
||||||
assert!(end_addr < 1024);
|
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
|
// Calculate address step rate, and check legality
|
||||||
let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64;
|
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
|
// Temporarily disable RAM mode while accessing into RAM
|
||||||
self.disable_ram_configuration()?;
|
self.disable_ram_configuration()?;
|
||||||
self.write_ram(data)?;
|
unsafe {
|
||||||
|
self.write_ram()?;
|
||||||
|
}
|
||||||
|
|
||||||
// Properly configure start_addr and end_addr
|
// Properly configure start_addr and end_addr
|
||||||
self.enable_ram_configuration(ram_dst)
|
self.enable_ram_configuration(ram_dst)
|
||||||
|
@ -622,14 +700,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data in RAM
|
// Write data in RAM
|
||||||
fn write_ram(&mut self, data: &[u8]) -> Result<(), Error<E>> {
|
unsafe fn write_ram(&mut self) -> Result<(), Error<E>> {
|
||||||
let mut vec: ArrayVec<[u8; 8192]> = ArrayVec::new();
|
self.spi.transfer(&mut RAM_VEC)
|
||||||
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)
|
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(Error::SPI)
|
.map_err(Error::SPI)
|
||||||
}
|
}
|
||||||
|
@ -654,6 +726,17 @@ where
|
||||||
}
|
}
|
||||||
Ok(error_count)
|
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
|
// Strong check for bytes passed to a register
|
||||||
|
|
|
@ -3,7 +3,6 @@ use embedded_hal::{
|
||||||
blocking::spi::Transfer,
|
blocking::spi::Transfer,
|
||||||
blocking::delay::DelayUs,
|
blocking::delay::DelayUs,
|
||||||
};
|
};
|
||||||
use log::info;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FPGAFlashError {
|
pub enum FPGAFlashError {
|
||||||
|
@ -12,13 +11,15 @@ pub enum FPGAFlashError {
|
||||||
ResetStatusError,
|
ResetStatusError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DATA: &'static [u8] = include_bytes!("../build/top.bin");
|
||||||
|
|
||||||
// A public method to flash iCE40 FPGA on Humpback
|
// A public method to flash iCE40 FPGA on Humpback
|
||||||
pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
||||||
SS: OutputPin,
|
SS: OutputPin,
|
||||||
RST: OutputPin,
|
RST: OutputPin,
|
||||||
DELAY: DelayUs<u32>,
|
DELAY: DelayUs<u32>,
|
||||||
DONE: InputPin>
|
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
|
// Data buffer setup
|
||||||
let mut dummy_byte :[u8; 1] = [0x00];
|
let mut dummy_byte :[u8; 1] = [0x00];
|
||||||
|
@ -62,7 +63,7 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
||||||
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
.map_err(|_| FPGAFlashError::NegotiationError)?;
|
||||||
|
|
||||||
// Send the whole image without interruption
|
// Send the whole image without interruption
|
||||||
for byte in data.into_iter() {
|
for byte in DATA.into_iter() {
|
||||||
let mut single_byte_slice = [*byte];
|
let mut single_byte_slice = [*byte];
|
||||||
spi.transfer(&mut single_byte_slice)
|
spi.transfer(&mut single_byte_slice)
|
||||||
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
.map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||||
|
@ -83,10 +84,8 @@ pub fn flash_ice40_fpga<SPI: Transfer<u8>,
|
||||||
_ => return Err(FPGAFlashError::ResetStatusError),
|
_ => return Err(FPGAFlashError::ResetStatusError),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Configuration successful!");
|
|
||||||
// Send at least another 49 clock cycles to activate IO pins (choosing same 13 bytes)
|
// 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)?;
|
spi.transfer(&mut dummy_13_bytes).map_err(|_| FPGAFlashError::SPICommunicationError)?;
|
||||||
info!("User I/O pins activated.");
|
|
||||||
Ok(())
|
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();
|
||||||
|
}
|
351
src/main.rs
351
src/main.rs
|
@ -1,39 +1,71 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(str_strip)]
|
||||||
#[macro_use]
|
use log::{ trace, debug, info, warn };
|
||||||
extern crate log;
|
use stm32h7xx_hal::hal::digital::v2::InputPin;
|
||||||
|
use stm32h7xx_hal::gpio::Speed;
|
||||||
use log::{trace, debug, info, warn};
|
|
||||||
|
|
||||||
use stm32h7xx_hal::hal::digital::v2::{
|
|
||||||
InputPin,
|
|
||||||
OutputPin,
|
|
||||||
};
|
|
||||||
use stm32h7xx_hal::{pac, prelude::*, spi};
|
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;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
|
use rtic::cyccnt::{Instant, U32Ext};
|
||||||
|
|
||||||
use firmware;
|
use heapless::Vec;
|
||||||
use firmware::{
|
use heapless::consts;
|
||||||
attenuator::Attenuator,
|
|
||||||
config_register::{
|
#[macro_use]
|
||||||
ConfigRegister,
|
pub mod bitmask_macro;
|
||||||
CFGMask,
|
pub mod spi_slave;
|
||||||
StatusMask,
|
pub mod cpld;
|
||||||
},
|
use crate::cpld::CPLD;
|
||||||
dds::{
|
pub mod config_register;
|
||||||
DDS,
|
pub mod attenuator;
|
||||||
DDSCFRMask,
|
pub mod dds;
|
||||||
},
|
pub mod nal_tcp_client;
|
||||||
cpld::{
|
use crate::nal_tcp_client::{ NetStorage, NetworkStack };
|
||||||
CPLD,
|
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"]
|
#[link_section = ".sram3.eth"]
|
||||||
mod logger;
|
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]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
@ -41,6 +73,16 @@ fn main() -> ! {
|
||||||
let mut cp = cortex_m::Peripherals::take().unwrap();
|
let mut cp = cortex_m::Peripherals::take().unwrap();
|
||||||
let dp = pac::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 pwr = dp.PWR.constrain();
|
||||||
let vos = pwr.freeze();
|
let vos = pwr.freeze();
|
||||||
|
|
||||||
|
@ -48,16 +90,17 @@ fn main() -> ! {
|
||||||
let ccdr = rcc
|
let ccdr = rcc
|
||||||
.use_hse(8.mhz())
|
.use_hse(8.mhz())
|
||||||
.sys_ck(400.mhz())
|
.sys_ck(400.mhz())
|
||||||
|
.hclk(200.mhz())
|
||||||
.pll1_q_ck(48.mhz())
|
.pll1_q_ck(48.mhz())
|
||||||
.pll1_r_ck(400.mhz())
|
.pll1_r_ck(400.mhz())
|
||||||
.freeze(vos, &dp.SYSCFG);
|
.freeze(vos, &dp.SYSCFG);
|
||||||
|
|
||||||
unsafe {
|
let delay = cp.SYST.delay(ccdr.clocks);
|
||||||
logger::enable_itm(&dp.DBGMCU, &mut cp.DCB, &mut cp.ITM);
|
|
||||||
}
|
|
||||||
logger::init();
|
|
||||||
|
|
||||||
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 gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA);
|
||||||
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
||||||
|
@ -65,29 +108,91 @@ fn main() -> ! {
|
||||||
let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD);
|
let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD);
|
||||||
let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE);
|
let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE);
|
||||||
let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF);
|
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();
|
let fpga_cdone = gpiod.pd15.into_pull_up_input();
|
||||||
|
|
||||||
match fpga_cdone.is_high() {
|
// Setup SPI interface
|
||||||
Ok(true) => info!("FPGA is ready."),
|
let fpga_cfg_spi = dp.SPI1.spi(
|
||||||
Ok(_) => info!("FPGA is in reset state."),
|
(fpga_sck, fpga_sdo, fpga_sdi),
|
||||||
Err(_) => info!("Error: Cannot read C_DONE"),
|
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
|
* Using SPI6
|
||||||
* SCLK -> PA5
|
* SCLK -> PA5 (af8)
|
||||||
* MOSI -> PB5
|
* MOSI -> PG14 (af5)
|
||||||
* MISO -> PA6
|
* MISO -> PA6 (af8)
|
||||||
* CS -> 0: PB12, 1: PA15, 2: PC7
|
* CS -> 0: PB12, 1: PA15, 2: PC7
|
||||||
*/
|
*/
|
||||||
|
let sclk = gpioa.pa5.into_alternate_af8().set_speed(Speed::VeryHigh);
|
||||||
let sclk = gpioa.pa5.into_alternate_af5();
|
let mosi = gpiog.pg14.into_alternate_af5().set_speed(Speed::VeryHigh);
|
||||||
let mosi = gpiob.pb5.into_alternate_af5();
|
let miso = gpioa.pa6.into_alternate_af8().set_speed(Speed::VeryHigh);
|
||||||
let miso = gpioa.pa6.into_alternate_af5();
|
|
||||||
|
|
||||||
|
|
||||||
let (cs0, cs1, cs2) = (
|
let (cs0, cs1, cs2) = (
|
||||||
gpiob.pb12.into_push_pull_output(),
|
gpiob.pb12.into_push_pull_output(),
|
||||||
gpioa.pa15.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 io_update = gpiob.pb15.into_push_pull_output();
|
||||||
|
|
||||||
let spi = dp.SPI1.spi(
|
let spi = dp.SPI6.spi(
|
||||||
(sclk, miso, mosi),
|
(sclk, miso, mosi),
|
||||||
spi::MODE_0,
|
spi::MODE_0,
|
||||||
3.mhz(),
|
10.mhz(),
|
||||||
ccdr.peripheral.SPI1,
|
ccdr.peripheral.SPI6,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
);
|
);
|
||||||
|
|
||||||
let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update);
|
let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update);
|
||||||
let parts = switch.split();
|
let parts = switch.split();
|
||||||
|
|
||||||
let mut config = ConfigRegister::new(parts.spi1);
|
let mut urukul = Urukul::new(
|
||||||
let mut att = Attenuator::new(parts.spi2);
|
parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7
|
||||||
let mut dds0 = DDS::new(parts.spi4, 25_000_000.0);
|
);
|
||||||
|
|
||||||
// Reset all DDS, set CLK_SEL to 0
|
urukul.reset().unwrap();
|
||||||
config.set_configurations(&mut [
|
// info!("Test value: {}", urukul.test().unwrap());
|
||||||
(CFGMask::RST, 1),
|
|
||||||
(CFGMask::IO_RST, 1),
|
|
||||||
(CFGMask::IO_UPDATE, 0)
|
|
||||||
]).unwrap();
|
|
||||||
|
|
||||||
config.set_configurations(&mut [
|
let mut mqtt_mux = MqttMux::new(urukul);
|
||||||
(CFGMask::IO_RST, 0),
|
|
||||||
(CFGMask::RST, 0),
|
|
||||||
(CFGMask::RF_SW, 13),
|
|
||||||
(CFGMask::DIV, 3)
|
|
||||||
]).unwrap();
|
|
||||||
|
|
||||||
dds0.init().unwrap();
|
// Time unit in ms
|
||||||
|
let mut time: u32 = 0;
|
||||||
|
|
||||||
dds0.set_configurations(&mut [
|
// Cycle counter for 1 ms
|
||||||
(DDSCFRMask::PDCLK_ENABLE, 0),
|
// This effectively provides a conversion from rtic unit to ms
|
||||||
(DDSCFRMask::READ_EFFECTIVE_FTW, 1),
|
let mut next_ms = Instant::now();
|
||||||
]).unwrap();
|
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
|
let tcp_stack = NetworkStack::new(&mut net_interface, sockets);
|
||||||
att.set_attenuation([
|
|
||||||
5.0, 31.5, 24.0, 0.0
|
|
||||||
]).unwrap();
|
|
||||||
|
|
||||||
dds0.set_single_tone_profile(1, 10_000_000.0, 0.0, 0.5).unwrap();
|
// Case dealt: Ethernet connection break down, neither side has timeout
|
||||||
config.set_configurations(&mut [
|
// Limitation: Timeout inequality will cause TCP socket state to desync
|
||||||
(CFGMask::PROFILE, 1),
|
// Probably fixed in latest smoltcp commit
|
||||||
]).unwrap();
|
let mut client = MqttClient::<consts::U256, _>::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 125)),
|
||||||
|
"Urukul",
|
||||||
|
tcp_stack,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// // Setup RAM configuration
|
let mut tick = false;
|
||||||
// dds0.set_configurations(&mut [
|
let mut has_subscribed = false;
|
||||||
// (DDSCFRMask::RAM_ENABLE, 1),
|
|
||||||
// (DDSCFRMask::RAM_PLAYBACK_DST, 2),
|
|
||||||
// ]).unwrap();
|
|
||||||
|
|
||||||
// // Configure RAM profile 0
|
loop {
|
||||||
// dds0.write_register(0x0E, &mut [
|
// Update time accumulator in ms
|
||||||
// 0x00, // Open
|
// Tick once every ms
|
||||||
// 0x09, 0xC4, // Address step rate (2500)
|
if Instant::now() > next_ms {
|
||||||
// 0xFF, 0xC0, // End at address 1023
|
tick = true;
|
||||||
// 0x00, 0x00, // Start at address 0
|
time += 1;
|
||||||
// 0x04, // Recirculate mode
|
next_ms += 400_000.cycles();
|
||||||
// ]).unwrap();
|
}
|
||||||
|
|
||||||
// debug!("{:#X?}", dds0.read_register(0x0E, &mut[
|
// eth Poll if necessary
|
||||||
// 0x00, 0x00, 0x00, 0x00,
|
// Do not poll if eth link is down
|
||||||
// 0x00, 0x00, 0x00, 0x00,
|
if tick && client.network_stack.update_delay(time) == 0 && eth_mac.phy_poll_link() {
|
||||||
// ]).unwrap());
|
client.network_stack.update(time);
|
||||||
|
}
|
||||||
|
|
||||||
// // Choose profile 0
|
// Process MQTT messages about Urukul/Control
|
||||||
// config.set_configurations(&mut [
|
let connection = client
|
||||||
// (CFGMask::PROFILE, 0),
|
.poll(|_client, topic, message, _properties| {
|
||||||
// ]).unwrap();
|
// 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
|
if connection && !has_subscribed && tick {
|
||||||
// dds0.set_configurations(&mut [
|
match client.subscribe("Urukul/Control/#", &[]) {
|
||||||
// (DDSCFRMask::RAM_PLAYBACK_DST, 0),
|
Ok(()) => has_subscribed = true,
|
||||||
// (DDSCFRMask::RAM_ENABLE, 0),
|
Err(minimq::Error::NotReady) => {},
|
||||||
// ]).unwrap();
|
_e => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1];
|
if connection && tick && (time % 3000) == 0 {
|
||||||
// ram_data[0] = 0x16;
|
client.publish("Urukul/Channel1/Switch", mqtt_mux
|
||||||
// for index in 0..1024 {
|
.get_switch_status_message(1)
|
||||||
// if index % 2 == 1 {
|
.unwrap()
|
||||||
// ram_data[(index * 4) + 1] = 0x3F;
|
.as_bytes(), QoS::AtMostOnce, &[])
|
||||||
// ram_data[(index * 4) + 2] = 0xFF;
|
.unwrap();
|
||||||
// } 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();
|
|
||||||
|
|
||||||
// config.set_configurations(&mut [
|
// Reset tick flag
|
||||||
// (CFGMask::PROFILE, 1),
|
tick = false;
|
||||||
// ]).unwrap();
|
}
|
||||||
|
|
||||||
// config.set_configurations(&mut [
|
|
||||||
// (CFGMask::PROFILE, 0),
|
|
||||||
// ]).unwrap();
|
|
||||||
|
|
||||||
// dds0.set_configurations(&mut [
|
|
||||||
// (DDSCFRMask::RAM_ENABLE, 1),
|
|
||||||
// ]).unwrap();
|
|
||||||
|
|
||||||
loop {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
127
src/mqtt_mux.rs
127
src/mqtt_mux.rs
|
@ -1,24 +1,18 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
use nom::combinator::{value, map, map_res, not, opt, all_consuming};
|
use nom::combinator::{value, map, map_res, opt, all_consuming};
|
||||||
use nom::sequence::{terminated, preceded, pair, delimited, tuple};
|
use nom::sequence::{terminated, preceded, pair};
|
||||||
use nom::bytes::complete::{take, tag, tag_no_case, take_while};
|
use nom::bytes::complete::{tag, tag_no_case, take_while};
|
||||||
use nom::character::complete::digit1;
|
use nom::character::complete::digit1;
|
||||||
use nom::character::is_space;
|
use nom::character::is_space;
|
||||||
use nom::branch::alt;
|
use nom::branch::{permutation, alt};
|
||||||
use nom::number::complete::{float, double};
|
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 embedded_hal::blocking::spi::Transfer;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use crate::ClockSource as UrukulClockSource;
|
use crate::urukul::ClockSource as UrukulClockSource;
|
||||||
use crate::ClockSource::*;
|
use crate::urukul::Urukul;
|
||||||
use crate::Urukul;
|
use crate::urukul::Error;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MqttTopic {
|
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),
|
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
|
// Read message parameter separator (optional comma and whitespace)
|
||||||
fn topic_separator<'a>(topic: &'a str) -> IResult<&'a str, ()> {
|
|
||||||
value((), tag("/"))(topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message separator parser
|
|
||||||
fn message_separator(message: &[u8]) -> IResult<&[u8], ()> {
|
fn message_separator(message: &[u8]) -> IResult<&[u8], ()> {
|
||||||
value(
|
|
||||||
(),
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
opt(
|
||||||
preceded(
|
tag(",")
|
||||||
tag("/"),
|
),
|
||||||
whitespace
|
whitespace
|
||||||
)
|
|
||||||
)
|
|
||||||
)(message)
|
)(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,18 +395,12 @@ fn clock_division_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser for one-command master clock setup message
|
// 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> {
|
fn clock_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
all_consuming(
|
all_consuming(
|
||||||
map(
|
map(
|
||||||
delimited(
|
permutation((
|
||||||
tag("{"),
|
|
||||||
tuple((
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
tag_no_case("source:"),
|
||||||
preceded(
|
|
||||||
tag("\"source\":"),
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
terminated(
|
terminated(
|
||||||
|
@ -421,28 +409,22 @@ fn clock_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
value(UrukulClockSource::MMCX, tag_no_case("MMCX")),
|
value(UrukulClockSource::MMCX, tag_no_case("MMCX")),
|
||||||
value(UrukulClockSource::SMA, tag_no_case("SMA"))
|
value(UrukulClockSource::SMA, tag_no_case("SMA"))
|
||||||
)),
|
)),
|
||||||
tag(",")
|
message_separator
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
tag_no_case("frequency:"),
|
||||||
preceded(
|
|
||||||
tag("\"frequency\":"),
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
terminated(
|
terminated(
|
||||||
read_frequency,
|
read_frequency,
|
||||||
tag(",")
|
message_separator
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
tag_no_case("division:"),
|
||||||
preceded(
|
|
||||||
tag("\"division\":"),
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
terminated(
|
terminated(
|
||||||
|
@ -450,14 +432,11 @@ fn clock_message(message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
digit1,
|
digit1,
|
||||||
|div: &[u8]| u8::from_str_radix(core::str::from_utf8(div).unwrap(), 10)
|
|div: &[u8]| u8::from_str_radix(core::str::from_utf8(div).unwrap(), 10)
|
||||||
),
|
),
|
||||||
whitespace
|
message_separator
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
tag("}")
|
|
||||||
),
|
|
||||||
|(src, freq, div): (UrukulClockSource, f64, u8)| MqttCommand::Clock(src, freq, div)
|
|(src, freq, div): (UrukulClockSource, f64, u8)| MqttCommand::Clock(src, freq, div)
|
||||||
)
|
)
|
||||||
)(message)
|
)(message)
|
||||||
|
@ -512,45 +491,22 @@ fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser for one-command singletone profile Command
|
// 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> {
|
fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> {
|
||||||
all_consuming(
|
all_consuming(
|
||||||
map(
|
map(
|
||||||
tuple((
|
permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("{"),
|
tag_no_case("frequency:"),
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
preceded(
|
terminated(
|
||||||
tag("\"frequency\":"),
|
read_frequency,
|
||||||
preceded(
|
message_separator
|
||||||
whitespace,
|
|
||||||
read_frequency
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
preceded(
|
preceded(
|
||||||
tag(","),
|
tag_no_case("phase:"),
|
||||||
preceded(
|
|
||||||
whitespace,
|
|
||||||
preceded(
|
|
||||||
tag("\"amplitude\":"),
|
|
||||||
preceded(
|
|
||||||
whitespace,
|
|
||||||
double
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag(","),
|
|
||||||
preceded(
|
|
||||||
whitespace,
|
|
||||||
preceded(
|
|
||||||
tag("\"phase\":"),
|
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
terminated(
|
terminated(
|
||||||
|
@ -562,18 +518,23 @@ fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8]
|
||||||
tag_no_case("deg")
|
tag_no_case("deg")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
message_separator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
preceded(
|
||||||
|
tag_no_case("amplitude:"),
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
tag("}")
|
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)
|
)(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,
|
digital::v2::OutputPin,
|
||||||
};
|
};
|
||||||
use crate::cpld::CPLD;
|
use crate::cpld::CPLD;
|
||||||
use crate::Error;
|
use crate::urukul::Error;
|
||||||
|
|
||||||
pub struct SPISlave<'a, SPI, CS0, CS1, CS2, GPIO> (
|
pub struct SPISlave<'a, SPI, CS0, CS1, CS2, GPIO> (
|
||||||
// SPI device to be multiplexed
|
// SPI device to be multiplexed
|
||||||
|
@ -29,7 +29,7 @@ impl<'a, SPI, CS0, CS1, CS2, GPIO> Parts<'a, SPI, CS0, CS1, CS2, GPIO> {
|
||||||
Parts {
|
Parts {
|
||||||
spi1: SPISlave(&cpld, 1, false),
|
spi1: SPISlave(&cpld, 1, false),
|
||||||
spi2: SPISlave(&cpld, 2, false),
|
spi2: SPISlave(&cpld, 2, false),
|
||||||
spi3: SPISlave(&cpld, 3, true),
|
spi3: SPISlave(&cpld, 3, false),
|
||||||
spi4: SPISlave(&cpld, 4, true),
|
spi4: SPISlave(&cpld, 4, true),
|
||||||
spi5: SPISlave(&cpld, 5, true),
|
spi5: SPISlave(&cpld, 5, true),
|
||||||
spi6: SPISlave(&cpld, 6, 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;
|
extern crate embedded_hal;
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
blocking::spi::Transfer,
|
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::ConfigRegister;
|
||||||
use crate::config_register::CFGMask;
|
use crate::config_register::CFGMask;
|
||||||
use crate::config_register::StatusMask;
|
use crate::config_register::StatusMask;
|
||||||
|
|
||||||
pub mod attenuator;
|
|
||||||
use crate::attenuator::Attenuator;
|
use crate::attenuator::Attenuator;
|
||||||
|
|
||||||
pub mod dds;
|
|
||||||
use crate::dds::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
|
* Enum for structuring error
|
||||||
*/
|
*/
|
||||||
|
@ -60,6 +41,7 @@ pub enum ClockSource {
|
||||||
pub struct Urukul<SPI> {
|
pub struct Urukul<SPI> {
|
||||||
config_register: ConfigRegister<SPI>,
|
config_register: ConfigRegister<SPI>,
|
||||||
attenuator: Attenuator<SPI>,
|
attenuator: Attenuator<SPI>,
|
||||||
|
multi_dds: DDS<SPI>,
|
||||||
dds: [DDS<SPI>; 4],
|
dds: [DDS<SPI>; 4],
|
||||||
f_master_clk: f64,
|
f_master_clk: f64,
|
||||||
}
|
}
|
||||||
|
@ -77,7 +59,9 @@ where
|
||||||
Urukul {
|
Urukul {
|
||||||
config_register: ConfigRegister::new(spi1),
|
config_register: ConfigRegister::new(spi1),
|
||||||
attenuator: Attenuator::new(spi2),
|
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
|
// Counter-intuitive to assign urukul clock before having a urukul
|
||||||
dds: [
|
dds: [
|
||||||
DDS::new(spi4, 25_000_000.0),
|
DDS::new(spi4, 25_000_000.0),
|
||||||
|
@ -85,7 +69,7 @@ where
|
||||||
DDS::new(spi6, 25_000_000.0),
|
DDS::new(spi6, 25_000_000.0),
|
||||||
DDS::new(spi7, 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,
|
f_master_clk: 100_000_000.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +134,7 @@ where
|
||||||
SPI: Transfer<u8, Error = E>
|
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 {
|
if channel < 4 {
|
||||||
self.config_register.get_status(StatusMask::RF_SW).map(|val| (val & (1 << channel)) != 0)
|
self.config_register.get_status(StatusMask::RF_SW).map(|val| (val & (1 << channel)) != 0)
|
||||||
} else {
|
} 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 {
|
if channel < 4 {
|
||||||
let prev = u32::from(self.config_register.get_status(StatusMask::RF_SW)?);
|
let prev = u32::from(self.config_register.get_status(StatusMask::RF_SW)?);
|
||||||
let next = {
|
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
|
// Change clock source through configuration register
|
||||||
self.set_clock_source(source)?;
|
self.set_clock_source(source)?;
|
||||||
|
|
||||||
|
@ -187,7 +171,7 @@ where
|
||||||
self.set_clock_division(division)
|
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
|
// Change clock source through configuration register
|
||||||
match source {
|
match source {
|
||||||
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
ClockSource::OSC => self.config_register.set_configurations(&mut [
|
||||||
|
@ -204,7 +188,7 @@ where
|
||||||
}.map(|_| ())
|
}.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
|
// Update master clock frequency
|
||||||
self.f_master_clk = frequency;
|
self.f_master_clk = frequency;
|
||||||
|
|
||||||
|
@ -212,7 +196,7 @@ where
|
||||||
self.set_dds_ref_clk()
|
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 {
|
match division {
|
||||||
1 => self.config_register.set_configurations(&mut [
|
1 => self.config_register.set_configurations(&mut [
|
||||||
(CFGMask::DIV, 1),
|
(CFGMask::DIV, 1),
|
||||||
|
@ -231,7 +215,7 @@ where
|
||||||
|
|
||||||
fn set_dds_ref_clk(&mut self) -> Result<(), Error<E>> {
|
fn set_dds_ref_clk(&mut self) -> Result<(), Error<E>> {
|
||||||
// Calculate reference clock frequency after clock division from configuration register
|
// 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
|
// Update all DDS chips on reference clock frequency
|
||||||
for dds_channel in 0..4 {
|
for dds_channel in 0..4 {
|
||||||
|
@ -240,14 +224,23 @@ where
|
||||||
Ok(())
|
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 {
|
if channel >= 4 || attenuation < 0.0 || attenuation > 31.5 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
self.attenuator.set_channel_attenuation(channel, attenuation)
|
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 {
|
if profile >= 8 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
|
@ -256,7 +249,7 @@ where
|
||||||
]).map(|_| ())
|
]).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 ||
|
if channel >= 4 || profile >= 8 || frequency < 0.0 || phase >= 360.0 ||
|
||||||
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
phase < 0.0 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
|
@ -264,28 +257,82 @@ where
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
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 {
|
if channel >= 4 || profile >= 8 || frequency < 0.0 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
|
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 {
|
if channel >= 4 || profile >= 8 || phase >= 360.0 || phase < 0.0 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
|
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 {
|
if channel >= 4 || profile >= 8 || amplitude < 0.0 || amplitude > 1.0 {
|
||||||
return Err(Error::ParameterError);
|
return Err(Error::ParameterError);
|
||||||
}
|
}
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
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>> {
|
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)
|
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