Merge branch 'master' into feature/livestream
This commit is contained in:
commit
e01d430e8e
128
Cargo.lock
generated
128
Cargo.lock
generated
@ -63,7 +63,7 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
|
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc_version",
|
"rustc_version 0.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -104,11 +104,11 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
version = "0.2.3"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
|
checksum = "57cdfa5d50aad6cb4d44dcab6101a7f79925bd59d82ca42f38a9856a28865374"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc_version",
|
"rustc_version 0.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -214,7 +214,7 @@ dependencies = [
|
|||||||
"easybench",
|
"easybench",
|
||||||
"miniconf",
|
"miniconf",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"num",
|
"num-complex",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -305,9 +305,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.2"
|
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 = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
@ -374,9 +374,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.93"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
|
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
@ -395,17 +395,18 @@ checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matrixmultiply"
|
name = "matrixmultiply"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1300bdbea33ec2836b01ff1f5a6eed8bad66d0c31f94d9b7993407a8b054c3a1"
|
checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rawpointer",
|
"rawpointer",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp23017"
|
name = "mcp23017"
|
||||||
version = "0.1.1"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/lucazulian/mcp23017.git?rev=523d71d#523d71dcb11fc0ea4bd9385ef2527ae7a7eee987"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c32fd6627e73f1cfa95c00ddcdcb5a6a6ddbd10b308d08588a502c018b6e12c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
]
|
]
|
||||||
@ -471,19 +472,6 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152"
|
checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
|
||||||
dependencies = [
|
|
||||||
"num-complex",
|
|
||||||
"num-integer",
|
|
||||||
"num-iter",
|
|
||||||
"num-rational",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-complex"
|
name = "num-complex"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -491,6 +479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
|
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -503,28 +492,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-iter"
|
|
||||||
version = "0.1.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-rational"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -561,6 +528,15 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@ -569,9 +545,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.26"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
|
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
@ -673,7 +649,16 @@ 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",
|
"semver 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||||
|
dependencies = [
|
||||||
|
"semver 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -688,7 +673,16 @@ 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 = [
|
dependencies = [
|
||||||
"semver-parser",
|
"semver-parser 0.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser 0.10.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -697,6 +691,15 @@ 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 = "semver-parser"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.126"
|
version = "1.0.126"
|
||||||
@ -740,9 +743,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.7.1"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97173c1ef35b0a09304cb3882eba594761243005847cbbf6124f966e8da6519a"
|
checksum = "11b5647cc4676e9358e6b15b6536b34e5b413e5ae946a06b3f85e713132bcdfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@ -809,8 +812,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32h7xx-hal"
|
name = "stm32h7xx-hal"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/quartiq/stm32h7xx-hal.git?rev=b0b8a93#b0b8a930b2c3bc5fcebc2e905b4c5e13360111a5"
|
||||||
checksum = "67034b80041bc33a48df1c1c435b6ae3bb18c35e42aa7e702ce8363b96793398"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
"bare-metal 1.0.0",
|
||||||
"cast",
|
"cast",
|
||||||
@ -827,9 +829,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.68"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87"
|
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -842,6 +844,12 @@ version = "1.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ufmt-write"
|
name = "ufmt-write"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -850,9 +858,9 @@ checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcell"
|
name = "vcell"
|
||||||
|
11
Cargo.toml
11
Cargo.toml
@ -45,20 +45,19 @@ ad9959 = { path = "ad9959" }
|
|||||||
miniconf = "0.1.0"
|
miniconf = "0.1.0"
|
||||||
shared-bus = {version = "0.2.2", features = ["cortex-m"] }
|
shared-bus = {version = "0.2.2", features = ["cortex-m"] }
|
||||||
serde-json-core = "0.4"
|
serde-json-core = "0.4"
|
||||||
|
mcp23017 = "1.0"
|
||||||
|
|
||||||
# rtt-target bump
|
# rtt-target bump
|
||||||
[dependencies.rtt-logger]
|
[dependencies.rtt-logger]
|
||||||
git = "https://github.com/quartiq/rtt-logger.git"
|
git = "https://github.com/quartiq/rtt-logger.git"
|
||||||
rev = "70b0eb5"
|
rev = "70b0eb5"
|
||||||
|
|
||||||
# rewrite
|
# fast double buffered DMA without poisoning and buffer swapping
|
||||||
[dependencies.mcp23017]
|
|
||||||
git = "https://github.com/lucazulian/mcp23017.git"
|
|
||||||
rev = "523d71d"
|
|
||||||
|
|
||||||
[dependencies.stm32h7xx-hal]
|
[dependencies.stm32h7xx-hal]
|
||||||
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
|
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
|
||||||
version = "0.9.0"
|
# version = "0.9.0"
|
||||||
|
git = "https://github.com/quartiq/stm32h7xx-hal.git"
|
||||||
|
rev = "b0b8a93"
|
||||||
|
|
||||||
# link.x section start/end
|
# link.x section start/end
|
||||||
[patch.crates-io.cortex-m-rt]
|
[patch.crates-io.cortex-m-rt]
|
||||||
|
@ -43,6 +43,7 @@ pub enum Mode {
|
|||||||
|
|
||||||
/// The configuration registers within the AD9959 DDS device. The values of each register are
|
/// The configuration registers within the AD9959 DDS device. The values of each register are
|
||||||
/// equivalent to the address.
|
/// equivalent to the address.
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub enum Register {
|
pub enum Register {
|
||||||
CSR = 0x00,
|
CSR = 0x00,
|
||||||
FR1 = 0x01,
|
FR1 = 0x01,
|
||||||
@ -596,6 +597,22 @@ impl ProfileSerializer {
|
|||||||
self.index += value.len() + 1;
|
self.index += value.len() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pad(&mut self) {
|
||||||
|
// Pad the buffer to 32-bit (4 byte) alignment by adding dummy writes to CSR and LSRR.
|
||||||
|
match self.index & 3 {
|
||||||
|
3 => {
|
||||||
|
// For a level of 3, we have to pad with 5 bytes to align things.
|
||||||
|
self.add_write(Register::CSR, &[(self.mode as u8) << 1]);
|
||||||
|
self.add_write(Register::LSRR, &[0, 0]);
|
||||||
|
}
|
||||||
|
2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]),
|
||||||
|
1 => self.add_write(Register::LSRR, &[0, 0]),
|
||||||
|
0 => {}
|
||||||
|
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the serialized profile as a slice of 32-bit words.
|
/// Get the serialized profile as a slice of 32-bit words.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
@ -604,21 +621,8 @@ impl ProfileSerializer {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A slice of `u32` words representing the serialized profile.
|
/// A slice of `u32` words representing the serialized profile.
|
||||||
pub fn finalize<'a>(&'a mut self) -> &[u32] {
|
pub fn finalize<'a>(&'a mut self) -> &'a [u32] {
|
||||||
// Pad the buffer to 32-bit alignment by adding dummy writes to CSR and LSRR.
|
self.pad();
|
||||||
let padding = 4 - (self.index % 4);
|
|
||||||
match padding {
|
|
||||||
1 => {
|
|
||||||
// For a pad size of 1, we have to pad with 5 bytes to align things.
|
|
||||||
self.add_write(Register::CSR, &[(self.mode as u8) << 1]);
|
|
||||||
self.add_write(Register::LSRR, &[0, 0]);
|
|
||||||
}
|
|
||||||
2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]),
|
|
||||||
3 => self.add_write(Register::LSRR, &[0, 0]),
|
|
||||||
4 => {}
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::slice::from_raw_parts::<'a, u32>(
|
core::slice::from_raw_parts::<'a, u32>(
|
||||||
&self.data as *const _ as *const u32,
|
&self.data as *const _ as *const u32,
|
||||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"], default-features = false }
|
serde = { version = "1.0", features = ["derive"], default-features = false }
|
||||||
num = { version = "0.4.0", default-features = false }
|
num-complex = { version = "0.4.0", features = ["serde"], default-features = false }
|
||||||
miniconf = "0.1"
|
miniconf = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -44,11 +44,11 @@ fn pll_bench() {
|
|||||||
let mut dut = PLL::default();
|
let mut dut = PLL::default();
|
||||||
println!(
|
println!(
|
||||||
"PLL::update(t, 12, 12): {}",
|
"PLL::update(t, 12, 12): {}",
|
||||||
bench_env(0x241, |x| dut.update(*x, 12, 12))
|
bench_env(Some(0x241), |x| dut.update(*x, 12, 12))
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"PLL::update(t, sf, sp): {}",
|
"PLL::update(t, sf, sp): {}",
|
||||||
bench_env((0x241, 21, 20), |(x, p, q)| dut.update(*x, *p, *q))
|
bench_env((Some(0x241), 21, 20), |(x, p, q)| dut.update(*x, *p, *q))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,8 @@ pub fn atan2(y: i32, x: i32) -> i32 {
|
|||||||
|
|
||||||
if sign.1 {
|
if sign.1 {
|
||||||
angle = angle.wrapping_neg();
|
angle = angle.wrapping_neg();
|
||||||
|
// Negation ends up in slightly faster assembly
|
||||||
|
// angle = !angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
angle
|
angle
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
pub use num::Complex;
|
pub use num_complex::Complex;
|
||||||
|
|
||||||
use super::{atan2, cossin};
|
use super::{atan2, cossin};
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ impl PLL {
|
|||||||
/// The signal's phase/frequency is reconstructed relative to the sampling period.
|
/// The signal's phase/frequency is reconstructed relative to the sampling period.
|
||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
/// * `x`: New input phase sample.
|
/// * `x`: New input phase sample or None if a sample has been missed.
|
||||||
/// * `shift_frequency`: Frequency error scaling. The frequency gain per update is
|
/// * `shift_frequency`: Frequency error scaling. The frequency gain per update is
|
||||||
/// `1/(1 << shift_frequency)`.
|
/// `1/(1 << shift_frequency)`.
|
||||||
/// * `shift_phase`: Phase error scaling. The phase gain is `1/(1 << shift_phase)`
|
/// * `shift_phase`: Phase error scaling. The phase gain is `1/(1 << shift_phase)`
|
||||||
@ -55,12 +55,13 @@ impl PLL {
|
|||||||
/// A tuple of instantaneous phase and frequency (the current phase increment).
|
/// A tuple of instantaneous phase and frequency (the current phase increment).
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
x: i32,
|
x: Option<i32>,
|
||||||
shift_frequency: u8,
|
shift_frequency: u8,
|
||||||
shift_phase: u8,
|
shift_phase: u8,
|
||||||
) -> (i32, i32) {
|
) -> (i32, i32) {
|
||||||
debug_assert!((1..=30).contains(&shift_frequency));
|
debug_assert!((1..=30).contains(&shift_frequency));
|
||||||
debug_assert!((1..=30).contains(&shift_phase));
|
debug_assert!((1..=30).contains(&shift_phase));
|
||||||
|
let f = if let Some(x) = x {
|
||||||
let e = x.wrapping_sub(self.f);
|
let e = x.wrapping_sub(self.f);
|
||||||
self.f = self.f.wrapping_add(
|
self.f = self.f.wrapping_add(
|
||||||
(1i32 << (shift_frequency - 1))
|
(1i32 << (shift_frequency - 1))
|
||||||
@ -69,12 +70,16 @@ impl PLL {
|
|||||||
>> shift_frequency,
|
>> shift_frequency,
|
||||||
);
|
);
|
||||||
self.x = x;
|
self.x = x;
|
||||||
let f = self.f.wrapping_add(
|
self.f.wrapping_add(
|
||||||
(1i32 << (shift_phase - 1))
|
(1i32 << (shift_phase - 1))
|
||||||
.wrapping_add(e)
|
.wrapping_add(e)
|
||||||
.wrapping_sub(self.y)
|
.wrapping_sub(self.y)
|
||||||
>> shift_phase,
|
>> shift_phase,
|
||||||
);
|
)
|
||||||
|
} else {
|
||||||
|
self.x = self.x.wrapping_add(self.f);
|
||||||
|
self.f
|
||||||
|
};
|
||||||
self.y = self.y.wrapping_add(f);
|
self.y = self.y.wrapping_add(f);
|
||||||
(self.y, f)
|
(self.y, f)
|
||||||
}
|
}
|
||||||
@ -86,7 +91,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mini() {
|
fn mini() {
|
||||||
let mut p = PLL::default();
|
let mut p = PLL::default();
|
||||||
let (y, f) = p.update(0x10000, 8, 4);
|
let (y, f) = p.update(Some(0x10000), 8, 4);
|
||||||
assert_eq!(y, 0x1100);
|
assert_eq!(y, 0x1100);
|
||||||
assert_eq!(f, y);
|
assert_eq!(f, y);
|
||||||
}
|
}
|
||||||
@ -100,7 +105,7 @@ mod tests {
|
|||||||
let mut x = 0i32;
|
let mut x = 0i32;
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
x = x.wrapping_add(f0);
|
x = x.wrapping_add(f0);
|
||||||
let (y, f) = p.update(x, shift.0, shift.1);
|
let (y, f) = p.update(Some(x), shift.0, shift.1);
|
||||||
if i > n / 4 {
|
if i > n / 4 {
|
||||||
assert_eq!(f.wrapping_sub(f0).abs() <= 1, true);
|
assert_eq!(f.wrapping_sub(f0).abs() <= 1, true);
|
||||||
}
|
}
|
||||||
|
@ -87,20 +87,3 @@ pub fn macc_i32(y0: i32, x: &[i32], a: &[i32], shift: u32) -> i32 {
|
|||||||
.fold(y0, |y, xa| y + xa);
|
.fold(y0, |y, xa| y + xa);
|
||||||
(y >> shift) as i32
|
(y >> shift) as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine high and low i32 into a single downscaled i32, saturating the type.
|
|
||||||
pub fn saturating_scale(lo: i32, hi: i32, shift: u32) -> i32 {
|
|
||||||
debug_assert!(shift & 31 == shift);
|
|
||||||
|
|
||||||
let shift_hi = 31 - shift;
|
|
||||||
debug_assert!(shift_hi & 31 == shift_hi);
|
|
||||||
|
|
||||||
let over = hi >> shift;
|
|
||||||
if over < -1 {
|
|
||||||
i32::MIN
|
|
||||||
} else if over > 0 {
|
|
||||||
i32::MAX
|
|
||||||
} else {
|
|
||||||
(lo >> shift) + (hi << shift_hi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -16,6 +16,27 @@ pub fn overflowing_sub(y: i32, x: i32) -> (i32, i8) {
|
|||||||
(delta, wrap)
|
(delta, wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combine high and low i32 into a single downscaled i32, saturating monotonically.
|
||||||
|
///
|
||||||
|
/// Args:
|
||||||
|
/// `lo`: LSB i32 to scale down by `shift` and range-extend with `hi`
|
||||||
|
/// `hi`: MSB i32 to scale up and extend `lo` with. Output will be clipped if
|
||||||
|
/// `hi` exceeds the output i32 range.
|
||||||
|
/// `shift`: Downscale `lo` by that many bits. Values from 1 to 32 inclusive
|
||||||
|
/// are valid.
|
||||||
|
pub fn saturating_scale(lo: i32, hi: i32, shift: u32) -> i32 {
|
||||||
|
debug_assert!(shift > 0);
|
||||||
|
debug_assert!(shift <= 32);
|
||||||
|
let hi_range = -1 << (shift - 1);
|
||||||
|
if hi <= hi_range {
|
||||||
|
i32::MIN - hi_range
|
||||||
|
} else if -hi <= hi_range {
|
||||||
|
hi_range - i32::MIN
|
||||||
|
} else {
|
||||||
|
(lo >> shift) + (hi << (32 - shift))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Overflow unwrapper.
|
/// Overflow unwrapper.
|
||||||
///
|
///
|
||||||
/// This is unwrapping as in the phase and overflow unwrapping context, not
|
/// This is unwrapping as in the phase and overflow unwrapping context, not
|
||||||
|
70
miniconf.py
70
miniconf.py
@ -16,6 +16,7 @@ from gmqtt import Client as MqttClient
|
|||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Miniconf:
|
class Miniconf:
|
||||||
"""An asynchronous API for controlling Miniconf devices using MQTT."""
|
"""An asynchronous API for controlling Miniconf devices using MQTT."""
|
||||||
|
|
||||||
@ -33,64 +34,64 @@ class Miniconf:
|
|||||||
client: A connected MQTT5 client.
|
client: A connected MQTT5 client.
|
||||||
prefix: The MQTT toptic prefix of the device to control.
|
prefix: The MQTT toptic prefix of the device to control.
|
||||||
"""
|
"""
|
||||||
self.uuid = uuid.uuid1()
|
|
||||||
self.request_id = 0
|
self.request_id = 0
|
||||||
self.client = client
|
self.client = client
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.inflight = {}
|
self.inflight = {}
|
||||||
self.client.on_message = self._handle_response
|
self.client.on_message = self._handle_response
|
||||||
self.client.subscribe(f'{prefix}/response/{self.uuid.hex}')
|
self.response_topic = f'{prefix}/response/{uuid.uuid1().hex}'
|
||||||
|
self.client.subscribe(self.response_topic)
|
||||||
|
|
||||||
def _handle_response(self, _client, _topic, payload, _qos, properties):
|
def _handle_response(self, _client, topic, payload, _qos, properties):
|
||||||
"""Callback function for when messages are received over MQTT.
|
"""Callback function for when messages are received over MQTT.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
_client: The MQTT client.
|
_client: The MQTT client.
|
||||||
_topic: The topic that the message was received on.
|
topic: The topic that the message was received on.
|
||||||
payload: The payload of the message.
|
payload: The payload of the message.
|
||||||
_qos: The quality-of-service level of the received packet
|
_qos: The quality-of-service level of the received packet
|
||||||
properties: A dictionary of properties associated with the message.
|
properties: A dictionary of properties associated with the message.
|
||||||
"""
|
"""
|
||||||
# Extract corrleation data from the properties
|
if topic == self.response_topic:
|
||||||
correlation_data = json.loads(properties['correlation_data'][0].decode('ascii'))
|
# Extract request_id corrleation data from the properties
|
||||||
|
request_id = int.from_bytes(
|
||||||
# Get the request ID from the correlation data
|
properties['correlation_data'][0], 'big')
|
||||||
request_id = correlation_data['request_id']
|
|
||||||
|
|
||||||
self.inflight[request_id].set_result(json.loads(payload))
|
self.inflight[request_id].set_result(json.loads(payload))
|
||||||
del self.inflight[request_id]
|
del self.inflight[request_id]
|
||||||
|
else:
|
||||||
|
LOGGER.warn('Unexpected message on "%s"', topic)
|
||||||
|
|
||||||
|
async def command(self, path, value, retain=True):
|
||||||
async def command(self, path, value):
|
|
||||||
"""Write the provided data to the specified path.
|
"""Write the provided data to the specified path.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: The path to write the message to.
|
path: The path to write the message to.
|
||||||
value: The value to write to the path.
|
value: The value to write to the path.
|
||||||
|
retain: Retain the MQTT message changing the setting
|
||||||
|
by the broker.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The response to the command as a dictionary.
|
The response to the command as a dictionary.
|
||||||
"""
|
"""
|
||||||
setting_topic = f'{self.prefix}/settings/{path}'
|
topic = f'{self.prefix}/settings/{path}'
|
||||||
response_topic = f'{self.prefix}/response/{self.uuid.hex}'
|
|
||||||
|
|
||||||
# Assign a unique identifier to this update request.
|
|
||||||
request_id = self.request_id
|
|
||||||
self.request_id += 1
|
|
||||||
assert request_id not in self.inflight, 'Invalid ID encountered'
|
|
||||||
|
|
||||||
correlation_data = json.dumps({
|
|
||||||
'request_id': request_id,
|
|
||||||
}).encode('ascii')
|
|
||||||
|
|
||||||
value = json.dumps(value)
|
|
||||||
LOGGER.info('Sending %s to "%s"', value, setting_topic)
|
|
||||||
fut = asyncio.get_running_loop().create_future()
|
fut = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
self.inflight[request_id] = fut
|
# Assign unique correlation data for response dispatch
|
||||||
self.client.publish(setting_topic, payload=value, qos=0, retain=True,
|
assert self.request_id not in self.inflight
|
||||||
response_topic=response_topic,
|
self.inflight[self.request_id] = fut
|
||||||
|
correlation_data = self.request_id.to_bytes(4, 'big')
|
||||||
|
self.request_id += 1
|
||||||
|
|
||||||
|
payload = json.dumps(value)
|
||||||
|
LOGGER.info('Sending "%s" to "%s"', value, topic)
|
||||||
|
|
||||||
|
self.client.publish(
|
||||||
|
topic, payload=payload, qos=0, retain=retain,
|
||||||
|
response_topic=self.response_topic,
|
||||||
correlation_data=correlation_data)
|
correlation_data=correlation_data)
|
||||||
|
|
||||||
return await fut
|
return await fut
|
||||||
|
|
||||||
|
|
||||||
@ -100,16 +101,20 @@ def main():
|
|||||||
description='Miniconf command line interface.',
|
description='Miniconf command line interface.',
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog='''Examples:
|
epilog='''Examples:
|
||||||
%(prog)s dt/sinara/stabilizer afe/0='"G2"' iir_ch/0/0=\
|
%(prog)s dt/sinara/dual-iir/00-11-22-33-aa-bb iir_ch/0/0=\
|
||||||
'{"y_min":-32767,"y_max":32767,"y_offset":0,"ba":[1.0,0,0,0,0]}'
|
'{"y_min":-32767,"y_max":32767,"y_offset":0,"ba":[1.0,0,0,0,0]}'
|
||||||
|
%(prog)s dt/sinara/lockin/00-11-22-33-aa-bb afe/0='"G2"'\
|
||||||
''')
|
''')
|
||||||
parser.add_argument('-v', '--verbose', action='count', default=0,
|
parser.add_argument('-v', '--verbose', action='count', default=0,
|
||||||
help='Increase logging verbosity')
|
help='Increase logging verbosity')
|
||||||
parser.add_argument('--broker', '-b', default='mqtt', type=str,
|
parser.add_argument('--broker', '-b', default='mqtt', type=str,
|
||||||
help='The MQTT broker address')
|
help='The MQTT broker address')
|
||||||
|
parser.add_argument('--no-retain', '-n', default=False,
|
||||||
|
action='store_true',
|
||||||
|
help='Do not retain the affected settings')
|
||||||
parser.add_argument('prefix', type=str,
|
parser.add_argument('prefix', type=str,
|
||||||
help='The MQTT topic prefix of the target')
|
help='The MQTT topic prefix of the target')
|
||||||
parser.add_argument('settings', metavar="KEY=VALUE", nargs='+',
|
parser.add_argument('settings', metavar="PATH=VALUE", nargs='+',
|
||||||
help='JSON encoded values for settings path keys.')
|
help='JSON encoded values for settings path keys.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -122,9 +127,10 @@ def main():
|
|||||||
|
|
||||||
async def configure_settings():
|
async def configure_settings():
|
||||||
interface = await Miniconf.create(args.prefix, args.broker)
|
interface = await Miniconf.create(args.prefix, args.broker)
|
||||||
for key_value in args.settings:
|
for setting in args.settings:
|
||||||
path, value = key_value.split("=", 1)
|
path, value = setting.split("=", 1)
|
||||||
response = await interface.command(path, json.loads(value))
|
response = await interface.command(path, json.loads(value),
|
||||||
|
not args.no_retain)
|
||||||
print(f'{path}: {response}')
|
print(f'{path}: {response}')
|
||||||
if response['code'] != 0:
|
if response['code'] != 0:
|
||||||
return response['code']
|
return response['code']
|
||||||
|
@ -2,19 +2,28 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use stabilizer::{hardware, net};
|
use core::sync::atomic::{fence, Ordering};
|
||||||
|
|
||||||
use miniconf::Miniconf;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use dsp::iir;
|
use dsp::iir;
|
||||||
use hardware::{
|
use stabilizer::{
|
||||||
Adc0Input, Adc1Input, AdcCode, AfeGain, Dac0Output, Dac1Output, DacCode,
|
flatten_closures,
|
||||||
DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1,
|
hardware::{
|
||||||
};
|
self,
|
||||||
|
adc::{Adc0Input, Adc1Input, AdcCode},
|
||||||
use net::{
|
afe::Gain,
|
||||||
BlockGenerator, NetworkUsers, Telemetry, TelemetryBuffer, NetworkState,
|
dac::{Dac0Output, Dac1Output, DacCode},
|
||||||
|
embedded_hal::digital::v2::InputPin,
|
||||||
|
hal,
|
||||||
|
system_timer::SystemTimer,
|
||||||
|
DigitalInput0, DigitalInput1, AFE0, AFE1,
|
||||||
|
},
|
||||||
|
net::{
|
||||||
|
miniconf::Miniconf,
|
||||||
|
serde::Deserialize,
|
||||||
|
telemetry::{Telemetry, TelemetryBuffer},
|
||||||
|
NetworkState, NetworkUsers,
|
||||||
|
data_stream::BlockGenerator,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCALE: f32 = i16::MAX as _;
|
const SCALE: f32 = i16::MAX as _;
|
||||||
@ -24,7 +33,7 @@ const IIR_CASCADE_LENGTH: usize = 1;
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Miniconf)]
|
#[derive(Clone, Copy, Debug, Deserialize, Miniconf)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
afe: [AfeGain; 2],
|
afe: [Gain; 2],
|
||||||
iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2],
|
iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2],
|
||||||
allow_hold: bool,
|
allow_hold: bool,
|
||||||
force_hold: bool,
|
force_hold: bool,
|
||||||
@ -35,7 +44,7 @@ impl Default for Settings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
// Analog frontend programmable gain amplifier gains (G1, G2, G5, G10)
|
// Analog frontend programmable gain amplifier gains (G1, G2, G5, G10)
|
||||||
afe: [AfeGain::G1, AfeGain::G1],
|
afe: [Gain::G1, Gain::G1],
|
||||||
// IIR filter tap gains are an array `[b0, b1, b2, a1, a2]` such that the
|
// IIR filter tap gains are an array `[b0, b1, b2, a1, a2]` such that the
|
||||||
// new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`.
|
// new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`.
|
||||||
// The array is `iir_state[channel-index][cascade-index][coeff-index]`.
|
// The array is `iir_state[channel-index][cascade-index][coeff-index]`.
|
||||||
@ -52,7 +61,7 @@ impl Default for Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)]
|
#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, monotonic = stabilizer::hardware::system_timer::SystemTimer)]
|
||||||
const APP: () = {
|
const APP: () = {
|
||||||
struct Resources {
|
struct Resources {
|
||||||
afes: (AFE0, AFE1),
|
afes: (AFE0, AFE1),
|
||||||
@ -72,7 +81,8 @@ const APP: () = {
|
|||||||
#[init(spawn=[telemetry, settings_update, ethernet_link])]
|
#[init(spawn=[telemetry, settings_update, ethernet_link])]
|
||||||
fn init(c: init::Context) -> init::LateResources {
|
fn init(c: init::Context) -> init::LateResources {
|
||||||
// Configure the microcontroller
|
// Configure the microcontroller
|
||||||
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
let (mut stabilizer, _pounder) =
|
||||||
|
hardware::setup::setup(c.core, c.device);
|
||||||
|
|
||||||
let mut network = NetworkUsers::new(
|
let mut network = NetworkUsers::new(
|
||||||
stabilizer.net.stack,
|
stabilizer.net.stack,
|
||||||
@ -110,7 +120,7 @@ const APP: () = {
|
|||||||
generator,
|
generator,
|
||||||
network,
|
network,
|
||||||
digital_inputs: stabilizer.digital_inputs,
|
digital_inputs: stabilizer.digital_inputs,
|
||||||
telemetry: net::TelemetryBuffer::default(),
|
telemetry: TelemetryBuffer::default(),
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,54 +144,66 @@ const APP: () = {
|
|||||||
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry, generator], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry, generator], priority=2)]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[link_section = ".itcm.process"]
|
#[link_section = ".itcm.process"]
|
||||||
fn process(c: process::Context) {
|
fn process(mut c: process::Context) {
|
||||||
let adc_samples = [
|
let process::Resources {
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
adcs: (ref mut adc0, ref mut adc1),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
dacs: (ref mut dac0, ref mut dac1),
|
||||||
];
|
ref digital_inputs,
|
||||||
|
ref settings,
|
||||||
let dac_samples = [
|
ref mut iir_state,
|
||||||
c.resources.dacs.0.acquire_buffer(),
|
ref mut telemetry,
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
} = c.resources;
|
||||||
];
|
|
||||||
|
|
||||||
let digital_inputs = [
|
let digital_inputs = [
|
||||||
c.resources.digital_inputs.0.is_high().unwrap(),
|
digital_inputs.0.is_high().unwrap(),
|
||||||
c.resources.digital_inputs.1.is_high().unwrap(),
|
digital_inputs.1.is_high().unwrap(),
|
||||||
];
|
];
|
||||||
|
telemetry.digital_inputs = digital_inputs;
|
||||||
|
|
||||||
let hold = c.resources.settings.force_hold
|
let hold =
|
||||||
|| (digital_inputs[1] && c.resources.settings.allow_hold);
|
settings.force_hold || (digital_inputs[1] && settings.allow_hold);
|
||||||
|
|
||||||
|
flatten_closures!(with_buffer, adc0, adc1, dac0, dac1, {
|
||||||
|
let adc_samples = [adc0, adc1];
|
||||||
|
let dac_samples = [dac0, dac1];
|
||||||
|
|
||||||
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
|
|
||||||
for channel in 0..adc_samples.len() {
|
for channel in 0..adc_samples.len() {
|
||||||
for sample in 0..adc_samples[0].len() {
|
adc_samples[channel]
|
||||||
let mut y = f32::from(adc_samples[channel][sample] as i16);
|
.iter()
|
||||||
for i in 0..c.resources.iir_state[channel].len() {
|
.zip(dac_samples[channel].iter_mut())
|
||||||
y = c.resources.settings.iir_ch[channel][i].update(
|
.map(|(ai, di)| {
|
||||||
&mut c.resources.iir_state[channel][i],
|
let x = f32::from(*ai as i16);
|
||||||
y,
|
let y = settings.iir_ch[channel]
|
||||||
hold,
|
.iter()
|
||||||
);
|
.zip(iir_state[channel].iter_mut())
|
||||||
}
|
.fold(x, |yi, (ch, state)| {
|
||||||
// Note(unsafe): The filter limits ensure that the value is in range.
|
ch.update(state, yi, hold)
|
||||||
|
});
|
||||||
|
// Note(unsafe): The filter limits must ensure that the value is in range.
|
||||||
// The truncation introduces 1/2 LSB distortion.
|
// The truncation introduces 1/2 LSB distortion.
|
||||||
let y = unsafe { y.to_int_unchecked::<i16>() };
|
let y: i16 = unsafe { y.to_int_unchecked() };
|
||||||
// Convert to DAC code
|
// Convert to DAC code
|
||||||
dac_samples[channel][sample] = DacCode::from(y).0;
|
*di = DacCode::from(y).0;
|
||||||
}
|
})
|
||||||
|
.last();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream the data.
|
// Stream the data.
|
||||||
c.resources.generator.send(&adc_samples, &dac_samples);
|
c.resources.generator.send(&adc_samples, &dac_samples);
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
telemetry.adcs =
|
||||||
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.dacs =
|
telemetry.dacs =
|
||||||
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.digital_inputs = digital_inputs;
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[network], spawn=[settings_update])]
|
#[idle(resources=[network], spawn=[settings_update])]
|
||||||
@ -238,27 +260,27 @@ const APP: () = {
|
|||||||
|
|
||||||
#[task(binds = ETH, priority = 1)]
|
#[task(binds = ETH, priority = 1)]
|
||||||
fn eth(_: eth::Context) {
|
fn eth(_: eth::Context) {
|
||||||
unsafe { stm32h7xx_hal::ethernet::interrupt_handler() }
|
unsafe { hal::ethernet::interrupt_handler() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI2, priority = 3)]
|
#[task(binds = SPI2, priority = 3)]
|
||||||
fn spi2(_: spi2::Context) {
|
fn spi2(_: spi2::Context) {
|
||||||
panic!("ADC0 input overrun");
|
panic!("ADC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI3, priority = 3)]
|
#[task(binds = SPI3, priority = 3)]
|
||||||
fn spi3(_: spi3::Context) {
|
fn spi3(_: spi3::Context) {
|
||||||
panic!("ADC1 input overrun");
|
panic!("ADC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI4, priority = 3)]
|
#[task(binds = SPI4, priority = 3)]
|
||||||
fn spi4(_: spi4::Context) {
|
fn spi4(_: spi4::Context) {
|
||||||
panic!("DAC0 output error");
|
panic!("DAC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI5, priority = 3)]
|
#[task(binds = SPI5, priority = 3)]
|
||||||
fn spi5(_: spi5::Context) {
|
fn spi5(_: spi5::Context) {
|
||||||
panic!("DAC1 output error");
|
panic!("DAC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -2,23 +2,31 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use embedded_hal::digital::v2::InputPin;
|
use core::sync::atomic::{fence, Ordering};
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
|
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
|
||||||
|
use stabilizer::{
|
||||||
use stabilizer::net;
|
flatten_closures,
|
||||||
|
hardware::{
|
||||||
use stabilizer::hardware::{
|
self,
|
||||||
design_parameters, setup, Adc0Input, Adc1Input, AdcCode, AfeGain,
|
adc::{Adc0Input, Adc1Input, AdcCode},
|
||||||
Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1,
|
afe::Gain,
|
||||||
InputStamper, SystemTimer, AFE0, AFE1,
|
dac::{Dac0Output, Dac1Output, DacCode},
|
||||||
|
design_parameters,
|
||||||
|
embedded_hal::digital::v2::InputPin,
|
||||||
|
hal,
|
||||||
|
input_stamper::InputStamper,
|
||||||
|
system_timer::SystemTimer,
|
||||||
|
DigitalInput0, DigitalInput1, AFE0, AFE1,
|
||||||
|
},
|
||||||
|
net::{
|
||||||
|
miniconf::Miniconf,
|
||||||
|
serde::Deserialize,
|
||||||
|
telemetry::{Telemetry, TelemetryBuffer},
|
||||||
|
NetworkState, NetworkUsers,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use miniconf::Miniconf;
|
|
||||||
use net::{NetworkState, NetworkUsers, Telemetry, TelemetryBuffer};
|
|
||||||
|
|
||||||
// A constant sinusoid to send on the DAC output.
|
// A constant sinusoid to send on the DAC output.
|
||||||
// Full-scale gives a +/- 10.24V amplitude waveform. Scale it down to give +/- 1V.
|
// Full-scale gives a +/- 10.24V amplitude waveform. Scale it down to give +/- 1V.
|
||||||
const ONE: i16 = ((1.0 / 10.24) * i16::MAX as f32) as _;
|
const ONE: i16 = ((1.0 / 10.24) * i16::MAX as f32) as _;
|
||||||
@ -45,7 +53,7 @@ enum LockinMode {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
afe: [AfeGain; 2],
|
afe: [Gain; 2],
|
||||||
lockin_mode: LockinMode,
|
lockin_mode: LockinMode,
|
||||||
|
|
||||||
pll_tc: [u8; 2],
|
pll_tc: [u8; 2],
|
||||||
@ -61,7 +69,7 @@ pub struct Settings {
|
|||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
afe: [AfeGain::G1; 2],
|
afe: [Gain::G1; 2],
|
||||||
|
|
||||||
lockin_mode: LockinMode::External,
|
lockin_mode: LockinMode::External,
|
||||||
|
|
||||||
@ -78,7 +86,7 @@ impl Default for Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)]
|
#[rtic::app(device = stabilizer::hardware::hal::stm32, peripherals = true, monotonic = stabilizer::hardware::system_timer::SystemTimer)]
|
||||||
const APP: () = {
|
const APP: () = {
|
||||||
struct Resources {
|
struct Resources {
|
||||||
afes: (AFE0, AFE1),
|
afes: (AFE0, AFE1),
|
||||||
@ -97,7 +105,8 @@ const APP: () = {
|
|||||||
#[init(spawn=[settings_update, telemetry, ethernet_link])]
|
#[init(spawn=[settings_update, telemetry, ethernet_link])]
|
||||||
fn init(c: init::Context) -> init::LateResources {
|
fn init(c: init::Context) -> init::LateResources {
|
||||||
// Configure the microcontroller
|
// Configure the microcontroller
|
||||||
let (mut stabilizer, _pounder) = setup(c.core, c.device);
|
let (mut stabilizer, _pounder) =
|
||||||
|
hardware::setup::setup(c.core, c.device);
|
||||||
|
|
||||||
let network = NetworkUsers::new(
|
let network = NetworkUsers::new(
|
||||||
stabilizer.net.stack,
|
stabilizer.net.stack,
|
||||||
@ -143,7 +152,7 @@ const APP: () = {
|
|||||||
network,
|
network,
|
||||||
digital_inputs: stabilizer.digital_inputs,
|
digital_inputs: stabilizer.digital_inputs,
|
||||||
timestamper: stabilizer.timestamper,
|
timestamper: stabilizer.timestamper,
|
||||||
telemetry: net::TelemetryBuffer::default(),
|
telemetry: TelemetryBuffer::default(),
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
@ -162,26 +171,22 @@ const APP: () = {
|
|||||||
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[link_section = ".itcm.process"]
|
#[link_section = ".itcm.process"]
|
||||||
fn process(c: process::Context) {
|
fn process(mut c: process::Context) {
|
||||||
let adc_samples = [
|
let process::Resources {
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
adcs: (ref mut adc0, ref mut adc1),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
dacs: (ref mut dac0, ref mut dac1),
|
||||||
];
|
ref settings,
|
||||||
|
ref mut telemetry,
|
||||||
let mut dac_samples = [
|
ref mut lockin,
|
||||||
c.resources.dacs.0.acquire_buffer(),
|
ref mut pll,
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
ref mut timestamper,
|
||||||
];
|
} = c.resources;
|
||||||
|
|
||||||
let lockin = c.resources.lockin;
|
|
||||||
let settings = c.resources.settings;
|
|
||||||
|
|
||||||
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
||||||
{
|
{
|
||||||
LockinMode::External => {
|
LockinMode::External => {
|
||||||
let timestamp =
|
let timestamp = timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
|
||||||
c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
|
let (pll_phase, pll_frequency) = pll.update(
|
||||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
|
||||||
timestamp.map(|t| t as i32),
|
timestamp.map(|t| t as i32),
|
||||||
settings.pll_tc[0],
|
settings.pll_tc[0],
|
||||||
settings.pll_tc[1],
|
settings.pll_tc[1],
|
||||||
@ -208,6 +213,13 @@ const APP: () = {
|
|||||||
reference_phase.wrapping_mul(settings.lockin_harmonic),
|
reference_phase.wrapping_mul(settings.lockin_harmonic),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
flatten_closures!(with_buffer, adc0, adc1, dac0, dac1, {
|
||||||
|
let adc_samples = [adc0, adc1];
|
||||||
|
let mut dac_samples = [dac0, dac1];
|
||||||
|
|
||||||
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
|
|
||||||
let output: Complex<i32> = adc_samples[0]
|
let output: Complex<i32> = adc_samples[0]
|
||||||
.iter()
|
.iter()
|
||||||
// Zip in the LO phase.
|
// Zip in the LO phase.
|
||||||
@ -240,13 +252,16 @@ const APP: () = {
|
|||||||
*sample = DacCode::from(value as i16).0;
|
*sample = DacCode::from(value as i16).0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
telemetry.adcs =
|
||||||
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.dacs =
|
telemetry.dacs =
|
||||||
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
|
|
||||||
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[network], spawn=[settings_update])]
|
#[idle(resources=[network], spawn=[settings_update])]
|
||||||
@ -309,27 +324,7 @@ const APP: () = {
|
|||||||
|
|
||||||
#[task(binds = ETH, priority = 1)]
|
#[task(binds = ETH, priority = 1)]
|
||||||
fn eth(_: eth::Context) {
|
fn eth(_: eth::Context) {
|
||||||
unsafe { stm32h7xx_hal::ethernet::interrupt_handler() }
|
unsafe { hal::ethernet::interrupt_handler() }
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = SPI2, priority = 3)]
|
|
||||||
fn spi2(_: spi2::Context) {
|
|
||||||
panic!("ADC0 input overrun");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = SPI3, priority = 3)]
|
|
||||||
fn spi3(_: spi3::Context) {
|
|
||||||
panic!("ADC1 input overrun");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = SPI4, priority = 3)]
|
|
||||||
fn spi4(_: spi4::Context) {
|
|
||||||
panic!("DAC0 output error");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[task(binds = SPI5, priority = 3)]
|
|
||||||
fn spi5(_: spi5::Context) {
|
|
||||||
panic!("DAC1 output error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -29,15 +29,9 @@
|
|||||||
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
||||||
///! and the ADC samples are available for processing.
|
///! and the ADC samples are available for processing.
|
||||||
///!
|
///!
|
||||||
///! The SPI peripheral internally has an 8- or 16-byte TX and RX FIFO, which corresponds to a 4- or
|
///! After a complete transfer of a batch of samples, the inactive buffer is available to the
|
||||||
///! 8-sample buffer for incoming ADC samples. During the handling of the DMA transfer completion,
|
///! user for processing. The processing must complete before the DMA transfer of the next batch
|
||||||
///! there is a small window where buffers are swapped over where it's possible that a sample could
|
///! completes.
|
||||||
///! be lost. In order to avoid this, the SPI RX FIFO is effectively used as a "sample overflow"
|
|
||||||
///! region and can buffer a number of samples until the next DMA transfer is configured. If a DMA
|
|
||||||
///! transfer is still not set in time, the SPI peripheral will generate an input-overrun interrupt.
|
|
||||||
///! This interrupt then serves as a means of detecting if samples have been lost, which will occur
|
|
||||||
///! whenever data processing takes longer than the collection period.
|
|
||||||
///!
|
|
||||||
///!
|
///!
|
||||||
///! ## Starting Data Collection
|
///! ## Starting Data Collection
|
||||||
///!
|
///!
|
||||||
@ -68,26 +62,26 @@
|
|||||||
///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
|
///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
|
||||||
///! value of 0 and ADC1's comparison to a counter value of 1.
|
///! value of 0 and ADC1's comparison to a counter value of 1.
|
||||||
///!
|
///!
|
||||||
///! In this implementation, single buffer mode DMA transfers are used because the SPI RX FIFO can
|
///! In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs
|
||||||
///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because
|
///! have finite depth, FIFO access is slower than AXISRAM access, and because the single
|
||||||
///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless
|
///! buffer mode DMA disable/enable and buffer update sequence is slow.
|
||||||
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
|
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::{SampleBuffer, SAMPLE_BUFFER_SIZE};
|
||||||
use super::timers;
|
use super::timers;
|
||||||
|
|
||||||
use hal::dma::{
|
use hal::dma::{
|
||||||
config::Priority,
|
config::Priority,
|
||||||
dma::{DMAReq, DmaConfig},
|
dma::{DMAReq, DmaConfig},
|
||||||
traits::TargetAddress,
|
traits::TargetAddress,
|
||||||
MemoryToPeripheral, PeripheralToMemory, Transfer,
|
DMAError, MemoryToPeripheral, PeripheralToMemory, Transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A type representing an ADC sample.
|
/// A type representing an ADC sample.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct AdcCode(pub u16);
|
pub struct AdcCode(pub u16);
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<f32> for AdcCode {
|
impl Into<f32> for AdcCode {
|
||||||
/// Convert raw ADC codes to/from voltage levels.
|
/// Convert raw ADC codes to/from voltage levels.
|
||||||
///
|
///
|
||||||
@ -119,8 +113,7 @@ static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
|
|||||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||||
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
||||||
#[link_section = ".axisram.buffers"]
|
#[link_section = ".axisram.buffers"]
|
||||||
static mut ADC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] =
|
static mut ADC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
|
||||||
|
|
||||||
macro_rules! adc_input {
|
macro_rules! adc_input {
|
||||||
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
|
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
|
||||||
@ -192,12 +185,11 @@ macro_rules! adc_input {
|
|||||||
|
|
||||||
/// Represents data associated with ADC.
|
/// Represents data associated with ADC.
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
transfer: Transfer<
|
transfer: Transfer<
|
||||||
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
||||||
PeripheralToMemory,
|
PeripheralToMemory,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut SampleBuffer,
|
||||||
hal::dma::DBTransfer,
|
hal::dma::DBTransfer,
|
||||||
>,
|
>,
|
||||||
trigger_transfer: Transfer<
|
trigger_transfer: Transfer<
|
||||||
@ -316,6 +308,7 @@ macro_rules! adc_input {
|
|||||||
// data stream is used to trigger a transfer completion interrupt.
|
// data stream is used to trigger a transfer completion interrupt.
|
||||||
let data_config = DmaConfig::default()
|
let data_config = DmaConfig::default()
|
||||||
.memory_increment(true)
|
.memory_increment(true)
|
||||||
|
.double_buffer(true)
|
||||||
.transfer_complete_interrupt($index == 1)
|
.transfer_complete_interrupt($index == 1)
|
||||||
.priority(Priority::VeryHigh);
|
.priority(Priority::VeryHigh);
|
||||||
|
|
||||||
@ -333,17 +326,14 @@ macro_rules! adc_input {
|
|||||||
Transfer::init(
|
Transfer::init(
|
||||||
data_stream,
|
data_stream,
|
||||||
spi,
|
spi,
|
||||||
// Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral.
|
// Note(unsafe): The ADC_BUF[$index] is "owned" by this peripheral.
|
||||||
// It shall not be used anywhere else in the module.
|
// It shall not be used anywhere else in the module.
|
||||||
unsafe { &mut ADC_BUF[$index][0] },
|
unsafe { &mut ADC_BUF[$index][0] },
|
||||||
None,
|
unsafe { Some(&mut ADC_BUF[$index][1]) },
|
||||||
data_config,
|
data_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It
|
|
||||||
// shall not be used anywhere else in the module.
|
|
||||||
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
|
||||||
transfer: data_transfer,
|
transfer: data_transfer,
|
||||||
trigger_transfer,
|
trigger_transfer,
|
||||||
clear_transfer,
|
clear_transfer,
|
||||||
@ -364,27 +354,17 @@ macro_rules! adc_input {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with ADC samples.
|
/// Wait for the transfer of the currently active buffer to complete,
|
||||||
|
/// then call a function on the now inactive buffer and acknowledge the
|
||||||
|
/// transfer complete flag.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// NOTE(unsafe): Memory safety and access ordering is not guaranteed
|
||||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
/// (see the HAL DMA docs).
|
||||||
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
|
||||||
// Wait for the transfer to fully complete before continuing. Note: If a device
|
where
|
||||||
// hangs up, check that this conditional is passing correctly, as there is no
|
F: FnOnce(&mut SampleBuffer) -> R,
|
||||||
// time-out checks here in the interest of execution speed.
|
{
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
unsafe { self.transfer.next_dbm_transfer_with(|buf, _current| f(buf)) }
|
||||||
|
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
|
||||||
|
|
||||||
// Start the next transfer.
|
|
||||||
self.transfer.clear_interrupts();
|
|
||||||
let (prev_buffer, _, _) =
|
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
|
||||||
|
|
||||||
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
|
||||||
self.next_buffer.replace(prev_buffer);
|
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,13 +52,13 @@
|
|||||||
///! served promptly after the transfer completes.
|
///! served promptly after the transfer completes.
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::{SampleBuffer, SAMPLE_BUFFER_SIZE};
|
||||||
use super::timers;
|
use super::timers;
|
||||||
|
|
||||||
use hal::dma::{
|
use hal::dma::{
|
||||||
dma::{DMAReq, DmaConfig},
|
dma::{DMAReq, DmaConfig},
|
||||||
traits::TargetAddress,
|
traits::TargetAddress,
|
||||||
MemoryToPeripheral, Transfer,
|
DMAError, MemoryToPeripheral, Transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
|
// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
|
||||||
@ -66,14 +66,14 @@ use hal::dma::{
|
|||||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||||
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
||||||
#[link_section = ".axisram.buffers"]
|
#[link_section = ".axisram.buffers"]
|
||||||
static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] =
|
static mut DAC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 3]; 2];
|
|
||||||
|
|
||||||
/// Custom type for referencing DAC output codes.
|
/// Custom type for referencing DAC output codes.
|
||||||
/// The internal integer is the raw code written to the DAC output register.
|
/// The internal integer is the raw code written to the DAC output register.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct DacCode(pub u16);
|
pub struct DacCode(pub u16);
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<f32> for DacCode {
|
impl Into<f32> for DacCode {
|
||||||
fn into(self) -> f32 {
|
fn into(self) -> f32 {
|
||||||
// The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
|
// The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
|
||||||
@ -105,7 +105,7 @@ macro_rules! dac_output {
|
|||||||
_channel: timers::tim2::$trigger_channel,
|
_channel: timers::tim2::$trigger_channel,
|
||||||
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { _channel, spi }
|
Self { spi, _channel }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the SPI and begin operating in a DMA-driven transfer mode.
|
/// Start the SPI and begin operating in a DMA-driven transfer mode.
|
||||||
@ -137,13 +137,12 @@ macro_rules! dac_output {
|
|||||||
|
|
||||||
/// Represents data associated with DAC.
|
/// Represents data associated with DAC.
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
|
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
|
||||||
transfer: Transfer<
|
transfer: Transfer<
|
||||||
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
$spi,
|
$spi,
|
||||||
MemoryToPeripheral,
|
MemoryToPeripheral,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut SampleBuffer,
|
||||||
hal::dma::DBTransfer,
|
hal::dma::DBTransfer,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
@ -198,33 +197,26 @@ macro_rules! dac_output {
|
|||||||
trigger_config,
|
trigger_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self { transfer }
|
||||||
transfer,
|
|
||||||
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
|
||||||
next_buffer: unsafe { Some(&mut DAC_BUF[$index][2]) },
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
self.transfer.start(|spi| spi.start_dma());
|
self.transfer.start(|spi| spi.start_dma());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquire the next output buffer to populate it with DAC codes.
|
/// Wait for the transfer of the currently active buffer to complete,
|
||||||
pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] {
|
/// then call a function on the now inactive buffer and acknowledge the
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
/// transfer complete flag.
|
||||||
// there is no time-out checks here in the interest of execution speed.
|
///
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
/// NOTE(unsafe): Memory safety and access ordering is not guaranteed
|
||||||
|
/// (see the HAL DMA docs).
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
|
||||||
|
where
|
||||||
// Start the next transfer.
|
F: FnOnce(&mut SampleBuffer) -> R,
|
||||||
let (prev_buffer, _, _) =
|
{
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
unsafe {
|
||||||
|
self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
|
||||||
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
}
|
||||||
self.next_buffer.replace(prev_buffer);
|
|
||||||
|
|
||||||
self.next_buffer.as_mut().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -50,5 +50,7 @@ pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2;
|
|||||||
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
||||||
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
||||||
|
|
||||||
|
pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE];
|
||||||
|
|
||||||
// The MQTT broker IPv4 address
|
// The MQTT broker IPv4 address
|
||||||
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
|
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
|
||||||
|
@ -1,28 +1,19 @@
|
|||||||
|
pub use embedded_hal;
|
||||||
///! Module for all hardware-specific setup of Stabilizer
|
///! Module for all hardware-specific setup of Stabilizer
|
||||||
use stm32h7xx_hal as hal;
|
pub use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
// Re-export for the DigitalInputs below:
|
pub mod adc;
|
||||||
pub use embedded_hal::digital::v2::InputPin;
|
pub mod afe;
|
||||||
|
pub mod cycle_counter;
|
||||||
mod adc;
|
pub mod dac;
|
||||||
mod afe;
|
|
||||||
mod configuration;
|
|
||||||
mod cycle_counter;
|
|
||||||
mod dac;
|
|
||||||
pub mod design_parameters;
|
pub mod design_parameters;
|
||||||
mod digital_input_stamper;
|
pub mod input_stamper;
|
||||||
mod eeprom;
|
|
||||||
pub mod pounder;
|
pub mod pounder;
|
||||||
mod system_timer;
|
pub mod setup;
|
||||||
mod timers;
|
pub mod system_timer;
|
||||||
|
|
||||||
pub use adc::{Adc0Input, Adc1Input, AdcCode};
|
mod eeprom;
|
||||||
pub use afe::Gain as AfeGain;
|
mod timers;
|
||||||
pub use cycle_counter::CycleCounter;
|
|
||||||
pub use dac::{Dac0Output, Dac1Output, DacCode};
|
|
||||||
pub use digital_input_stamper::InputStamper;
|
|
||||||
pub use pounder::DdsOutput;
|
|
||||||
pub use system_timer::SystemTimer;
|
|
||||||
|
|
||||||
// Type alias for the analog front-end (AFE) for ADC0.
|
// Type alias for the analog front-end (AFE) for ADC0.
|
||||||
pub type AFE0 = afe::ProgrammableGainAmplifier<
|
pub type AFE0 = afe::ProgrammableGainAmplifier<
|
||||||
@ -52,8 +43,6 @@ pub type NetworkStack = smoltcp_nal::NetworkStack<
|
|||||||
|
|
||||||
pub type EthernetPhy = hal::ethernet::phy::LAN8742A<hal::ethernet::EthernetMAC>;
|
pub type EthernetPhy = hal::ethernet::phy::LAN8742A<hal::ethernet::EthernetMAC>;
|
||||||
|
|
||||||
pub use configuration::{setup, PounderDevices, StabilizerDevices};
|
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
@ -30,16 +30,16 @@ pub trait AttenuatorInterface {
|
|||||||
// Read all the channels, modify the channel of interest, and write all the channels back.
|
// Read all the channels, modify the channel of interest, and write all the channels back.
|
||||||
// This ensures the staging register and the output register are always in sync.
|
// This ensures the staging register and the output register are always in sync.
|
||||||
let mut channels = [0_u8; 4];
|
let mut channels = [0_u8; 4];
|
||||||
self.read_all_attenuators(&mut channels)?;
|
self.transfer_attenuators(&mut channels)?;
|
||||||
|
|
||||||
// The lowest 2 bits of the 8-bit shift register on the attenuator are ignored. Shift the
|
// The lowest 2 bits of the 8-bit shift register on the attenuator are ignored. Shift the
|
||||||
// attenuator code into the upper 6 bits of the register value. Note that the attenuator
|
// attenuator code into the upper 6 bits of the register value. Note that the attenuator
|
||||||
// treats inputs as active-low, so the code is inverted before writing.
|
// treats inputs as active-low, so the code is inverted before writing.
|
||||||
channels[channel as usize] = (!attenuation_code) << 2;
|
channels[channel as usize] = !(attenuation_code << 2);
|
||||||
self.write_all_attenuators(&channels)?;
|
self.transfer_attenuators(&mut channels)?;
|
||||||
|
|
||||||
// Finally, latch the output of the updated channel to force it into an active state.
|
// Finally, latch the output of the updated channel to force it into an active state.
|
||||||
self.latch_attenuators(channel)?;
|
self.latch_attenuator(channel)?;
|
||||||
|
|
||||||
Ok(attenuation_code as f32 / 2.0)
|
Ok(attenuation_code as f32 / 2.0)
|
||||||
}
|
}
|
||||||
@ -57,8 +57,8 @@ pub trait AttenuatorInterface {
|
|||||||
// Reading the data always shifts data out of the staging registers, so we perform a
|
// Reading the data always shifts data out of the staging registers, so we perform a
|
||||||
// duplicate write-back to ensure the staging register is always equal to the output
|
// duplicate write-back to ensure the staging register is always equal to the output
|
||||||
// register.
|
// register.
|
||||||
self.read_all_attenuators(&mut channels)?;
|
self.transfer_attenuators(&mut channels)?;
|
||||||
self.write_all_attenuators(&channels)?;
|
self.transfer_attenuators(&mut channels)?;
|
||||||
|
|
||||||
// The attenuation code is stored in the upper 6 bits of the register, where each LSB
|
// The attenuation code is stored in the upper 6 bits of the register, where each LSB
|
||||||
// represents 0.5 dB. The attenuator stores the code as active-low, so inverting the result
|
// represents 0.5 dB. The attenuator stores the code as active-low, so inverting the result
|
||||||
@ -74,13 +74,10 @@ pub trait AttenuatorInterface {
|
|||||||
|
|
||||||
fn reset_attenuators(&mut self) -> Result<(), Error>;
|
fn reset_attenuators(&mut self) -> Result<(), Error>;
|
||||||
|
|
||||||
fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error>;
|
fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error>;
|
||||||
fn read_all_attenuators(
|
|
||||||
|
fn transfer_attenuators(
|
||||||
&mut self,
|
&mut self,
|
||||||
channels: &mut [u8; 4],
|
channels: &mut [u8; 4],
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn write_all_attenuators(
|
|
||||||
&mut self,
|
|
||||||
channels: &[u8; 4],
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,11 @@
|
|||||||
///! compile-time-known register update sequence needed for the application, the serialization
|
///! compile-time-known register update sequence needed for the application, the serialization
|
||||||
///! process can be done once and then register values can be written into a pre-computed serialized
|
///! process can be done once and then register values can be written into a pre-computed serialized
|
||||||
///! buffer to avoid the software overhead of much of the serialization process.
|
///! buffer to avoid the software overhead of much of the serialization process.
|
||||||
|
use log::warn;
|
||||||
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::{hrtimer::HighResTimerE, QspiInterface};
|
use super::{hrtimer::HighResTimerE, QspiInterface};
|
||||||
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
||||||
use stm32h7xx_hal as hal;
|
|
||||||
|
|
||||||
/// The DDS profile update stream.
|
/// The DDS profile update stream.
|
||||||
pub struct DdsOutput {
|
pub struct DdsOutput {
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
|
use super::hal;
|
||||||
|
use embedded_hal::{adc::OneShot, blocking::spi::Transfer};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod attenuators;
|
pub mod attenuators;
|
||||||
mod dds_output;
|
pub mod dds_output;
|
||||||
pub mod hrtimer;
|
pub mod hrtimer;
|
||||||
mod rf_power;
|
pub mod rf_power;
|
||||||
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
#[cfg(feature = "pounder_v1_1")]
|
||||||
pub mod timestamp;
|
pub mod timestamp;
|
||||||
|
|
||||||
pub use dds_output::DdsOutput;
|
pub enum GpioPin {
|
||||||
|
Led4Green = 0,
|
||||||
use super::hal;
|
Led5Red = 1,
|
||||||
|
Led6Green = 2,
|
||||||
use attenuators::AttenuatorInterface;
|
Led7Red = 3,
|
||||||
use rf_power::PowerMeasurementInterface;
|
Led8Green = 4,
|
||||||
|
Led9Red = 5,
|
||||||
use embedded_hal::{adc::OneShot, blocking::spi::Transfer};
|
AttLe0 = 8,
|
||||||
|
AttLe1 = 8 + 1,
|
||||||
const EXT_CLK_SEL_PIN: u8 = 8 + 7;
|
AttLe2 = 8 + 2,
|
||||||
#[allow(dead_code)]
|
AttLe3 = 8 + 3,
|
||||||
const OSC_EN_N_PIN: u8 = 8 + 6;
|
AttRstN = 8 + 5,
|
||||||
const ATT_RST_N_PIN: u8 = 8 + 5;
|
OscEnN = 8 + 6,
|
||||||
const ATT_LE3_PIN: u8 = 8 + 3;
|
ExtClkSel = 8 + 7,
|
||||||
const ATT_LE2_PIN: u8 = 8 + 2;
|
}
|
||||||
const ATT_LE1_PIN: u8 = 8 + 1;
|
|
||||||
const ATT_LE0_PIN: u8 = 8;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -37,13 +37,15 @@ pub enum Error {
|
|||||||
Adc,
|
Adc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The numerical value (discriminant) of the Channel enum is the index in the attenuator shift
|
||||||
|
/// register as well as the attenuator latch enable signal index on the GPIO extender.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum Channel {
|
pub enum Channel {
|
||||||
In0,
|
In0 = 0,
|
||||||
In1,
|
Out0 = 1,
|
||||||
Out0,
|
In1 = 2,
|
||||||
Out1,
|
Out1 = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||||
@ -80,14 +82,14 @@ pub struct DdsClockConfig {
|
|||||||
pub external_clock: bool,
|
pub external_clock: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<ad9959::Channel> for Channel {
|
impl From<Channel> for ad9959::Channel {
|
||||||
/// Translate pounder channels to DDS output channels.
|
/// Translate pounder channels to DDS output channels.
|
||||||
fn into(self) -> ad9959::Channel {
|
fn from(other: Channel) -> Self {
|
||||||
match self {
|
match other {
|
||||||
Channel::In0 => ad9959::Channel::Two,
|
Channel::In0 => Self::Two,
|
||||||
Channel::In1 => ad9959::Channel::Four,
|
Channel::In1 => Self::Four,
|
||||||
Channel::Out0 => ad9959::Channel::One,
|
Channel::Out0 => Self::One,
|
||||||
Channel::Out1 => ad9959::Channel::Three,
|
Channel::Out1 => Self::Three,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,41 +302,35 @@ impl PounderDevices {
|
|||||||
adc2_in_p,
|
adc2_in_p,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configure power-on-default state for pounder. All LEDs are on, on-board oscillator
|
// Configure power-on-default state for pounder. All LEDs are off, on-board oscillator
|
||||||
// selected, attenuators out of reset. Note that testing indicates the output state needs to
|
// selected and enabled, attenuators out of reset. Note that testing indicates the
|
||||||
// be set first to properly update the output registers.
|
// output state needs to be set first to properly update the output registers.
|
||||||
devices
|
devices
|
||||||
.mcp23017
|
.mcp23017
|
||||||
.all_pin_mode(mcp23017::PinMode::OUTPUT)
|
.all_pin_mode(mcp23017::PinMode::OUTPUT)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
devices
|
devices
|
||||||
.mcp23017
|
.mcp23017
|
||||||
.write_gpio(mcp23017::Port::GPIOA, 0x3F)
|
.write_gpio(mcp23017::Port::GPIOA, 0x00)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
devices
|
devices
|
||||||
.mcp23017
|
.mcp23017
|
||||||
.write_gpio(mcp23017::Port::GPIOB, 1 << 5)
|
.write_gpio(mcp23017::Port::GPIOB, 0x2F)
|
||||||
.map_err(|_| Error::I2c)?;
|
|
||||||
|
|
||||||
devices
|
|
||||||
.mcp23017
|
|
||||||
.digital_write(EXT_CLK_SEL_PIN, false)
|
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
|
|
||||||
Ok(devices)
|
Ok(devices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttenuatorInterface for PounderDevices {
|
impl attenuators::AttenuatorInterface for PounderDevices {
|
||||||
/// Reset all of the attenuators to a power-on default state.
|
/// Reset all of the attenuators to a power-on default state.
|
||||||
fn reset_attenuators(&mut self) -> Result<(), Error> {
|
fn reset_attenuators(&mut self) -> Result<(), Error> {
|
||||||
self.mcp23017
|
self.mcp23017
|
||||||
.digital_write(ATT_RST_N_PIN, false)
|
.write_gpio(mcp23017::Port::GPIOB, 0x0f)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
// TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is
|
// Duration of one I2C transaction is sufficiently long.
|
||||||
// sufficient. Document the delay here.
|
|
||||||
self.mcp23017
|
self.mcp23017
|
||||||
.digital_write(ATT_RST_N_PIN, true)
|
.write_gpio(mcp23017::Port::GPIOB, 0x2f)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -344,31 +340,24 @@ impl AttenuatorInterface for PounderDevices {
|
|||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
/// * `channel` - The attenuator channel to latch.
|
/// * `channel` - The attenuator channel to latch.
|
||||||
fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error> {
|
fn latch_attenuator(&mut self, channel: Channel) -> Result<(), Error> {
|
||||||
let pin = match channel {
|
let pin = channel as u8;
|
||||||
Channel::In0 => ATT_LE0_PIN,
|
|
||||||
Channel::In1 => ATT_LE2_PIN,
|
|
||||||
Channel::Out0 => ATT_LE1_PIN,
|
|
||||||
Channel::Out1 => ATT_LE3_PIN,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.mcp23017
|
self.mcp23017
|
||||||
.digital_write(pin, true)
|
.write_gpio(mcp23017::Port::GPIOB, 0x2f & !(1 << pin))
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
// TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is
|
// Duration of one I2C transaction is sufficiently long.
|
||||||
// sufficient. Document the delay here.
|
|
||||||
self.mcp23017
|
self.mcp23017
|
||||||
.digital_write(pin, false)
|
.write_gpio(mcp23017::Port::GPIOB, 0x2f)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the raw attenuation codes stored in the attenuator shift registers.
|
/// Read the raw attenuation codes stored in the attenuator shift registers.
|
||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
/// * `channels` - A slice to store the channel readings into.
|
/// * `channels` - A 4 byte slice to be shifted into the
|
||||||
fn read_all_attenuators(
|
/// attenuators and to contain the data shifted out.
|
||||||
|
fn transfer_attenuators(
|
||||||
&mut self,
|
&mut self,
|
||||||
channels: &mut [u8; 4],
|
channels: &mut [u8; 4],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -378,26 +367,9 @@ impl AttenuatorInterface for PounderDevices {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the attenuator shift registers.
|
|
||||||
///
|
|
||||||
/// Args:
|
|
||||||
/// * `channels` - The data to write into the attenuators.
|
|
||||||
fn write_all_attenuators(
|
|
||||||
&mut self,
|
|
||||||
channels: &[u8; 4],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut result = [0_u8; 4];
|
|
||||||
result.clone_from_slice(channels);
|
|
||||||
self.attenuator_spi
|
|
||||||
.transfer(&mut result)
|
|
||||||
.map_err(|_| Error::Spi)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerMeasurementInterface for PounderDevices {
|
impl rf_power::PowerMeasurementInterface for PounderDevices {
|
||||||
/// Sample an ADC channel.
|
/// Sample an ADC channel.
|
||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
use super::{Channel, Error};
|
use super::{Channel, Error};
|
||||||
|
|
||||||
/// Provide an interface to measure RF input power in dB.
|
/// Provide an interface to measure RF input power in dBm.
|
||||||
pub trait PowerMeasurementInterface {
|
pub trait PowerMeasurementInterface {
|
||||||
fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
|
fn sample_converter(&mut self, channel: Channel) -> Result<f32, Error>;
|
||||||
|
|
||||||
/// Measure the power of an input channel in dBm.
|
/// Measure the power of an input channel in dBm.
|
||||||
///
|
///
|
||||||
/// Note: This function assumes the input channel is connected to an AD8363 output.
|
|
||||||
///
|
|
||||||
/// Args:
|
/// Args:
|
||||||
/// * `channel` - The pounder channel to measure the power of in dBm.
|
/// * `channel` - The pounder input channel to measure the power of.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// Power in dBm after the digitally controlled attenuator before the amplifier.
|
||||||
fn measure_power(&mut self, channel: Channel) -> Result<f32, Error> {
|
fn measure_power(&mut self, channel: Channel) -> Result<f32, Error> {
|
||||||
let analog_measurement = self.sample_converter(channel)?;
|
let analog_measurement = self.sample_converter(channel)?;
|
||||||
|
|
||||||
// The AD8363 with VSET connected to VOUT provides an output voltage of 51.7 mV/dB at
|
// The AD8363 with VSET connected to VOUT provides an output voltage of 51.7 mV/dB at
|
||||||
// 100MHz. It also indicates a y-intercept of -58dBm.
|
// 100MHz with an intercept of -58 dBm.
|
||||||
Ok(analog_measurement / 0.0517 - 58.0)
|
// It is placed behind a 20 dB tap.
|
||||||
|
Ok(analog_measurement * (1. / 0.0517) + (-58. + 20.))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,50 +13,24 @@
|
|||||||
///! Once the timer is configured, an input capture is configured to record the timer count
|
///! Once the timer is configured, an input capture is configured to record the timer count
|
||||||
///! register. The input capture is configured to utilize an internal trigger for the input capture.
|
///! register. The input capture is configured to utilize an internal trigger for the input capture.
|
||||||
///! The internal trigger is selected such that when a sample is generated on ADC0, the input
|
///! The internal trigger is selected such that when a sample is generated on ADC0, the input
|
||||||
///! capture is simultaneously triggered. This results in the input capture triggering identically
|
///! capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the
|
||||||
///! to when the ADC samples the input.
|
///! batch size. This results in the input capture triggering identically to when the ADC samples
|
||||||
///!
|
///! the last sample of the batch. That sample is then available for processing by the user.
|
||||||
///! Once the input capture is properly configured, a DMA transfer is configured to collect all of
|
use crate::hardware::{design_parameters, timers};
|
||||||
///! timestamps. The DMA transfer collects 1 timestamp for each ADC sample collected. In order to
|
use core::convert::TryFrom;
|
||||||
///! avoid potentially losing a timestamp for a sample, the DMA transfer operates in double-buffer
|
|
||||||
///! mode. As soon as the DMA transfer completes, the hardware automatically swaps over to a second
|
|
||||||
///! buffer to continue capturing. This alleviates timing sensitivities of the DMA transfer
|
|
||||||
///! schedule.
|
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer};
|
|
||||||
|
|
||||||
use crate::hardware::{design_parameters::SAMPLE_BUFFER_SIZE, timers};
|
|
||||||
|
|
||||||
// Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the
|
|
||||||
// working data provided to the application. These buffers must exist in a DMA-accessible memory
|
|
||||||
// region. Note that AXISRAM is not initialized on boot, so their initial contents are undefined.
|
|
||||||
#[link_section = ".axisram.buffers"]
|
|
||||||
static mut BUF: [[u16; SAMPLE_BUFFER_SIZE]; 3] = [[0; SAMPLE_BUFFER_SIZE]; 3];
|
|
||||||
|
|
||||||
/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
|
/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
|
||||||
pub struct Timestamper {
|
pub struct Timestamper {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
timer: timers::PounderTimestampTimer,
|
timer: timers::PounderTimestampTimer,
|
||||||
transfer: Transfer<
|
capture_channel: timers::tim8::Channel1InputCapture,
|
||||||
hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
|
||||||
timers::tim8::Channel1InputCapture,
|
|
||||||
PeripheralToMemory,
|
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
|
||||||
hal::dma::DBTransfer,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timestamper {
|
impl Timestamper {
|
||||||
/// Construct the pounder sample timestamper.
|
/// Construct the pounder sample timestamper.
|
||||||
///
|
///
|
||||||
/// # Note
|
|
||||||
/// The DMA is immediately configured after instantiation. It will not collect any samples
|
|
||||||
/// until the sample timer begins to cause input capture triggers.
|
|
||||||
///
|
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `timestamp_timer` - The timer peripheral used for capturing timestamps from.
|
/// * `timestamp_timer` - The timer peripheral used for capturing timestamps from.
|
||||||
/// * `stream` - The DMA stream to use for collecting timestamps.
|
|
||||||
/// * `capture_channel` - The input capture channel for collecting timestamps.
|
/// * `capture_channel` - The input capture channel for collecting timestamps.
|
||||||
/// * `sampling_timer` - The stabilizer ADC sampling timer.
|
/// * `sampling_timer` - The stabilizer ADC sampling timer.
|
||||||
/// * `_clock_input` - The input pin for the external clock from Pounder.
|
/// * `_clock_input` - The input pin for the external clock from Pounder.
|
||||||
@ -65,18 +39,12 @@ impl Timestamper {
|
|||||||
/// The new pounder timestamper in an operational state.
|
/// The new pounder timestamper in an operational state.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut timestamp_timer: timers::PounderTimestampTimer,
|
mut timestamp_timer: timers::PounderTimestampTimer,
|
||||||
stream: hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
|
||||||
capture_channel: timers::tim8::Channel1,
|
capture_channel: timers::tim8::Channel1,
|
||||||
sampling_timer: &mut timers::SamplingTimer,
|
sampling_timer: &mut timers::SamplingTimer,
|
||||||
_clock_input: hal::gpio::gpioa::PA0<
|
_clock_input: hal::gpio::gpioa::PA0<
|
||||||
hal::gpio::Alternate<hal::gpio::AF3>,
|
hal::gpio::Alternate<hal::gpio::AF3>,
|
||||||
>,
|
>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let config = DmaConfig::default()
|
|
||||||
.memory_increment(true)
|
|
||||||
.circular_buffer(true)
|
|
||||||
.double_buffer(true);
|
|
||||||
|
|
||||||
// The sampling timer should generate a trigger output when CH1 comparison occurs.
|
// The sampling timer should generate a trigger output when CH1 comparison occurs.
|
||||||
sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
|
sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
|
||||||
|
|
||||||
@ -85,64 +53,39 @@ impl Timestamper {
|
|||||||
timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1);
|
timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1);
|
||||||
|
|
||||||
// The capture channel should capture whenever the trigger input occurs.
|
// The capture channel should capture whenever the trigger input occurs.
|
||||||
let input_capture = capture_channel
|
let mut input_capture = capture_channel
|
||||||
.into_input_capture(timers::tim8::CaptureSource1::TRC);
|
.into_input_capture(timers::tim8::CaptureSource1::TRC);
|
||||||
input_capture.listen_dma();
|
|
||||||
|
|
||||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
// Capture at the batch period.
|
||||||
let data_transfer: Transfer<_, _, PeripheralToMemory, _, _> =
|
input_capture.configure_prescaler(
|
||||||
Transfer::init(
|
timers::Prescaler::try_from(
|
||||||
stream,
|
design_parameters::SAMPLE_BUFFER_SIZE_LOG2,
|
||||||
input_capture,
|
)
|
||||||
// Note(unsafe): BUF[0] and BUF[1] are "owned" by this peripheral.
|
.unwrap(),
|
||||||
// They shall not be used anywhere else in the module.
|
|
||||||
unsafe { &mut BUF[0] },
|
|
||||||
unsafe { Some(&mut BUF[1]) },
|
|
||||||
config,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
timer: timestamp_timer,
|
timer: timestamp_timer,
|
||||||
transfer: data_transfer,
|
capture_channel: input_capture,
|
||||||
|
|
||||||
// Note(unsafe): BUF[2] is "owned" by this peripheral. It shall not be used anywhere
|
|
||||||
// else in the module.
|
|
||||||
next_buffer: unsafe { Some(&mut BUF[2]) },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the DMA transfer for collecting timestamps.
|
/// Start collecting timestamps.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
self.transfer
|
self.capture_channel.enable();
|
||||||
.start(|capture_channel| capture_channel.enable());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the period of the underlying timestamp timer.
|
/// Update the period of the underlying timestamp timer.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn update_period(&mut self, period: u16) {
|
pub fn update_period(&mut self, period: u16) {
|
||||||
self.timer.set_period_ticks(period);
|
self.timer.set_period_ticks(period);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with timestamps.
|
/// Obtain a timestamp.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A reference to the underlying buffer that has been filled with timestamps.
|
/// A `Result` potentially indicating capture overflow and containing a `Option` of a captured
|
||||||
#[allow(dead_code)]
|
/// timestamp.
|
||||||
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
pub fn latest_timestamp(&mut self) -> Result<Option<u16>, Option<u16>> {
|
||||||
// Wait for the transfer to fully complete before continuing.
|
self.capture_channel.latest_capture()
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as there is
|
|
||||||
// no time-out checks here in the interest of execution speed.
|
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
|
||||||
|
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
|
||||||
|
|
||||||
// Start the next transfer.
|
|
||||||
let (prev_buffer, _, _) =
|
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
|
||||||
|
|
||||||
self.next_buffer.replace(prev_buffer); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,10 @@ use smoltcp_nal::smoltcp;
|
|||||||
use embedded_hal::digital::v2::{InputPin, OutputPin};
|
use embedded_hal::digital::v2::{InputPin, OutputPin};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
adc, afe, cycle_counter::CycleCounter, dac, design_parameters,
|
adc, afe, cycle_counter::CycleCounter, dac, design_parameters, eeprom,
|
||||||
digital_input_stamper, eeprom, pounder, system_timer, timers, DdsOutput,
|
input_stamper::InputStamper, pounder, pounder::dds_output::DdsOutput,
|
||||||
DigitalInput0, DigitalInput1, EthernetPhy, NetworkStack, AFE0, AFE1,
|
system_timer, timers, DigitalInput0, DigitalInput1, EthernetPhy,
|
||||||
|
NetworkStack, AFE0, AFE1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NUM_TCP_SOCKETS: usize = 4;
|
const NUM_TCP_SOCKETS: usize = 4;
|
||||||
@ -105,7 +106,7 @@ pub struct StabilizerDevices {
|
|||||||
pub afes: (AFE0, AFE1),
|
pub afes: (AFE0, AFE1),
|
||||||
pub adcs: (adc::Adc0Input, adc::Adc1Input),
|
pub adcs: (adc::Adc0Input, adc::Adc1Input),
|
||||||
pub dacs: (dac::Dac0Output, dac::Dac1Output),
|
pub dacs: (dac::Dac0Output, dac::Dac1Output),
|
||||||
pub timestamper: digital_input_stamper::InputStamper,
|
pub timestamper: InputStamper,
|
||||||
pub adc_dac_timer: timers::SamplingTimer,
|
pub adc_dac_timer: timers::SamplingTimer,
|
||||||
pub timestamp_timer: timers::TimestampTimer,
|
pub timestamp_timer: timers::TimestampTimer,
|
||||||
pub net: NetworkDevices,
|
pub net: NetworkDevices,
|
||||||
@ -256,11 +257,6 @@ pub fn setup(
|
|||||||
let dma_streams =
|
let dma_streams =
|
||||||
hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
|
hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
|
||||||
|
|
||||||
// Early, before the DMA1 peripherals (#272)
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
|
||||||
let dma2_streams =
|
|
||||||
hal::dma::dma::StreamsTuple::new(device.DMA2, ccdr.peripheral.DMA2);
|
|
||||||
|
|
||||||
// Configure timer 2 to trigger conversions for the ADC
|
// Configure timer 2 to trigger conversions for the ADC
|
||||||
let mut sampling_timer = {
|
let mut sampling_timer = {
|
||||||
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
||||||
@ -537,10 +533,7 @@ pub fn setup(
|
|||||||
|
|
||||||
let input_stamper = {
|
let input_stamper = {
|
||||||
let trigger = gpioa.pa3.into_alternate_af2();
|
let trigger = gpioa.pa3.into_alternate_af2();
|
||||||
digital_input_stamper::InputStamper::new(
|
InputStamper::new(trigger, timestamp_timer_channels.ch4)
|
||||||
trigger,
|
|
||||||
timestamp_timer_channels.ch4,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let digital_inputs = {
|
let digital_inputs = {
|
||||||
@ -836,7 +829,7 @@ pub fn setup(
|
|||||||
let scl = gpiob.pb8.into_alternate_af4().set_open_drain();
|
let scl = gpiob.pb8.into_alternate_af4().set_open_drain();
|
||||||
let i2c1 = device.I2C1.i2c(
|
let i2c1 = device.I2C1.i2c(
|
||||||
(scl, sda),
|
(scl, sda),
|
||||||
100.khz(),
|
400.khz(),
|
||||||
ccdr.peripheral.I2C1,
|
ccdr.peripheral.I2C1,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
);
|
);
|
||||||
@ -980,7 +973,6 @@ pub fn setup(
|
|||||||
|
|
||||||
pounder::timestamp::Timestamper::new(
|
pounder::timestamp::Timestamper::new(
|
||||||
timestamp_timer,
|
timestamp_timer,
|
||||||
dma2_streams.0,
|
|
||||||
tim8_channels.ch1,
|
tim8_channels.ch1,
|
||||||
&mut sampling_timer,
|
&mut sampling_timer,
|
||||||
etr_pin,
|
etr_pin,
|
@ -1,5 +1,6 @@
|
|||||||
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
||||||
use super::hal;
|
use super::hal;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use hal::stm32::{
|
use hal::stm32::{
|
||||||
// TIM1 and TIM8 have identical registers.
|
// TIM1 and TIM8 have identical registers.
|
||||||
@ -34,6 +35,8 @@ pub enum TriggerSource {
|
|||||||
|
|
||||||
/// Prescalers for externally-supplied reference clocks.
|
/// Prescalers for externally-supplied reference clocks.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(TryFromPrimitive)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum Prescaler {
|
pub enum Prescaler {
|
||||||
Div1 = 0b00,
|
Div1 = 0b00,
|
||||||
Div2 = 0b01,
|
Div2 = 0b01,
|
||||||
@ -353,6 +356,21 @@ macro_rules! timer_channels {
|
|||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
|
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the input capture prescaler.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `psc` - Prescaler exponent.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn configure_prescaler(&mut self, prescaler: super::Prescaler) {
|
||||||
|
// Note(unsafe): This channel owns all access to the specific timer channel.
|
||||||
|
// Only atomic operations on completed on the timer registers.
|
||||||
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
|
// Note(unsafe): Enum values are all valid.
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
|
regs.[< $ccmrx _input >]().modify(|_, w| unsafe {
|
||||||
|
w.[< ic $index psc >]().bits(prescaler as u8)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note(unsafe): This manually implements DMA support for input-capture channels. This
|
// Note(unsafe): This manually implements DMA support for input-capture channels. This
|
||||||
|
16
src/lib.rs
16
src/lib.rs
@ -1,8 +1,18 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
|
||||||
|
/// Macro to reduce rightward drift when calling the same closure-based API
|
||||||
|
/// on multiple structs simultaneously, e.g. when accessing DMA buffers.
|
||||||
|
/// This could be improved a bit using the tuple-based style from `mutex-trait`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! flatten_closures {
|
||||||
|
($fn:ident, $e:ident, $fun:block) => {
|
||||||
|
$e.$fn(|$e| $fun ).unwrap()
|
||||||
|
};
|
||||||
|
($fn:ident, $e:ident, $($es:ident),+, $fun:block) => {
|
||||||
|
$e.$fn(|$e| flatten_closures!($fn, $($es),*, $fun)).unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
///! Respones to settings updates are sent without quality-of-service guarantees, so there's no
|
///! Respones to settings updates are sent without quality-of-service guarantees, so there's no
|
||||||
///! guarantee that the requestee will be informed that settings have been applied.
|
///! guarantee that the requestee will be informed that settings have been applied.
|
||||||
use heapless::String;
|
use heapless::String;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
||||||
use crate::hardware::design_parameters::MQTT_BROKER;
|
use crate::hardware::design_parameters::MQTT_BROKER;
|
||||||
@ -102,7 +103,7 @@ where
|
|||||||
let path = match topic.strip_prefix(prefix) {
|
let path = match topic.strip_prefix(prefix) {
|
||||||
// For paths, we do not want to include the leading slash.
|
// For paths, we do not want to include the leading slash.
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
if path.len() > 0 {
|
if !path.is_empty() {
|
||||||
&path[1..]
|
&path[1..]
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
@ -116,9 +117,8 @@ where
|
|||||||
|
|
||||||
let message: SettingsResponse = settings
|
let message: SettingsResponse = settings
|
||||||
.string_set(path.split('/').peekable(), message)
|
.string_set(path.split('/').peekable(), message)
|
||||||
.and_then(|_| {
|
.map(|_| {
|
||||||
update = true;
|
update = true;
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
@ -5,31 +5,29 @@
|
|||||||
///! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data
|
///! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data
|
||||||
///! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines
|
///! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines
|
||||||
///! related to Stabilizer networking operations.
|
///! related to Stabilizer networking operations.
|
||||||
|
pub use heapless;
|
||||||
|
pub use miniconf;
|
||||||
|
pub use serde;
|
||||||
|
|
||||||
|
pub mod messages;
|
||||||
|
pub mod miniconf_client;
|
||||||
|
pub mod network_processor;
|
||||||
|
pub mod shared;
|
||||||
|
pub mod telemetry;
|
||||||
|
pub mod data_stream;
|
||||||
|
|
||||||
|
use crate::hardware::{cycle_counter::CycleCounter, EthernetPhy, NetworkStack};
|
||||||
|
use messages::{MqttMessage, SettingsResponse};
|
||||||
|
use miniconf_client::MiniconfClient;
|
||||||
|
use network_processor::NetworkProcessor;
|
||||||
|
use shared::NetworkManager;
|
||||||
|
use telemetry::TelemetryClient;
|
||||||
|
|
||||||
|
use core::fmt::Write;
|
||||||
use heapless::String;
|
use heapless::String;
|
||||||
use miniconf::Miniconf;
|
use miniconf::Miniconf;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use core::fmt::Write;
|
|
||||||
|
|
||||||
mod data_stream;
|
|
||||||
mod messages;
|
|
||||||
mod miniconf_client;
|
|
||||||
mod network_processor;
|
|
||||||
mod shared;
|
|
||||||
mod telemetry;
|
|
||||||
|
|
||||||
pub use data_stream::BlockGenerator;
|
|
||||||
|
|
||||||
use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack};
|
|
||||||
use data_stream::DataStream;
|
|
||||||
use messages::{MqttMessage, SettingsResponse};
|
|
||||||
|
|
||||||
pub use miniconf_client::MiniconfClient;
|
|
||||||
pub use network_processor::NetworkProcessor;
|
|
||||||
pub use shared::NetworkManager;
|
|
||||||
use smoltcp_nal::embedded_nal::SocketAddr;
|
|
||||||
pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient};
|
|
||||||
|
|
||||||
pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>;
|
pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
///! The network processir is a small taks to regularly process incoming data over ethernet, handle
|
///! The network processir is a small taks to regularly process incoming data over ethernet, handle
|
||||||
///! the ethernet PHY state, and reset the network as appropriate.
|
///! the ethernet PHY state, and reset the network as appropriate.
|
||||||
use super::{NetworkReference, UpdateState};
|
use super::{NetworkReference, UpdateState};
|
||||||
use crate::hardware::{CycleCounter, EthernetPhy};
|
use crate::hardware::{cycle_counter::CycleCounter, EthernetPhy};
|
||||||
|
|
||||||
/// Processor for managing network hardware.
|
/// Processor for managing network hardware.
|
||||||
pub struct NetworkProcessor {
|
pub struct NetworkProcessor {
|
||||||
|
@ -16,7 +16,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use super::NetworkReference;
|
use super::NetworkReference;
|
||||||
use crate::hardware::{
|
use crate::hardware::{
|
||||||
design_parameters::MQTT_BROKER, AdcCode, AfeGain, DacCode,
|
adc::AdcCode, afe::Gain, dac::DacCode, design_parameters::MQTT_BROKER,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The telemetry client for reporting telemetry data over MQTT.
|
/// The telemetry client for reporting telemetry data over MQTT.
|
||||||
@ -73,7 +73,7 @@ impl TelemetryBuffer {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// The finalized telemetry structure that can be serialized and reported.
|
/// The finalized telemetry structure that can be serialized and reported.
|
||||||
pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry {
|
pub fn finalize(self, afe0: Gain, afe1: Gain) -> Telemetry {
|
||||||
let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
|
let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
|
||||||
let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
|
let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user