forked from M-Labs/thermostat
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
f323c1be63 |
286
Cargo.lock
generated
286
Cargo.lock
generated
@ -2,30 +2,29 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aligned"
|
name = "aligned"
|
||||||
version = "0.3.4"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c19796bd8d477f1a9d4ac2465b464a8b1359474f06a96bb3cda650b4fca309bf"
|
checksum = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as-slice",
|
"as-slice",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "as-slice"
|
name = "as-slice"
|
||||||
version = "0.1.4"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6"
|
checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.12.3",
|
||||||
"generic-array 0.13.2",
|
"generic-array 0.13.2",
|
||||||
"generic-array 0.14.4",
|
|
||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bare-metal"
|
name = "bare-metal"
|
||||||
@ -36,23 +35,11 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bare-metal"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.1"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitfield"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@ -83,21 +70,20 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m"
|
name = "cortex-m"
|
||||||
version = "0.6.4"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88cdafeafba636c00c467ded7f1587210725a1adfab0c24028a7844b87738263"
|
checksum = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aligned",
|
"aligned",
|
||||||
"bare-metal 0.2.5",
|
"bare-metal",
|
||||||
"bitfield",
|
|
||||||
"volatile-register",
|
"volatile-register",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m-log"
|
name = "cortex-m-log"
|
||||||
version = "0.6.2"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d63959cb1e003dd97233fee6762351540253237eadf06fcdcb98cbfa3f9be4a"
|
checksum = "978caafe65d1023d38b00c76b83564788fc351d954a5005fb72cf992c0d61458"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-semihosting",
|
"cortex-m-semihosting",
|
||||||
@ -106,9 +92,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cortex-m-rt"
|
name = "cortex-m-rt"
|
||||||
version = "0.6.13"
|
version = "0.6.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb"
|
checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m-rt-macros",
|
"cortex-m-rt-macros",
|
||||||
"r0",
|
"r0",
|
||||||
@ -134,31 +120,13 @@ dependencies = [
|
|||||||
"cortex-m",
|
"cortex-m",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "eeprom24x"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f680e8d81a559a97de04c5fab25f17f22a55770120c868ef8fbdea6398d44107"
|
|
||||||
dependencies = [
|
|
||||||
"embedded-hal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-dma"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46c8c02e4347a0267ca60813c952017f4c5948c232474c6010a381a337f1bda4"
|
|
||||||
dependencies = [
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-hal"
|
name = "embedded-hal"
|
||||||
version = "0.2.4"
|
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 = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb"
|
checksum = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nb 0.1.3",
|
"nb",
|
||||||
"void",
|
"void",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -181,36 +149,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "hash2hwaddr"
|
||||||
version = "0.14.4"
|
version = "0.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
checksum = "857afb5ee9e767c3a73b2ad7212b6deea0c3761a27db1e20ea0ed57ee352cfef"
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
|
|
||||||
dependencies = [
|
|
||||||
"as-slice",
|
|
||||||
"generic-array 0.13.2",
|
|
||||||
"hash32",
|
|
||||||
"serde",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@ -220,45 +162,36 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "managed"
|
name = "managed"
|
||||||
version = "0.7.2"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
|
checksum = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
|
||||||
dependencies = [
|
|
||||||
"nb 1.0.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nb"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "5.1.2"
|
version = "5.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"version_check",
|
"version_check",
|
||||||
@ -266,9 +199,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"libm",
|
"libm",
|
||||||
@ -282,45 +215,28 @@ checksum = "4e20e6499bbbc412f280b04a42346b356c6fa0753d5fd22b7bd752ff34c778ee"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "panic-semihosting"
|
name = "panic-semihosting"
|
||||||
version = "0.5.4"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aed16eb761d0ee9161dd1319cb38c8007813b20f9720a5a682b283e7b8cdfe58"
|
checksum = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-semihosting",
|
"cortex-m-semihosting",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "postcard"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b3e3f5c2e9a91383c6594ec68aa2dfdfe19a3c86f34b088ba7203f2483d2682f"
|
|
||||||
dependencies = [
|
|
||||||
"heapless",
|
|
||||||
"postcard-cobs",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "postcard-cobs"
|
|
||||||
version = "0.1.5-pre"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.24"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.7"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@ -361,36 +277,6 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-json-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cbf406405ada9ef326ca78677324ac66994ff348fc48a16030be08caeed29825"
|
|
||||||
dependencies = [
|
|
||||||
"heapless",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@ -405,17 +291,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32-eth"
|
name = "stm32-eth"
|
||||||
version = "0.2.0"
|
version = "0.1.2"
|
||||||
source = "git+https://github.com/stm32-rs/stm32-eth.git#4d6b29bf1ecdd1f68e5bc304a3d4f170049896c8"
|
source = "git+https://github.com/stm32-rs/stm32-eth.git#2c5dce379b85a31fb0b9c58a028b6454be1727aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aligned",
|
"aligned",
|
||||||
"cortex-m",
|
"log",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
"stm32f4xx-hal",
|
"stm32f4xx-hal",
|
||||||
"volatile-register",
|
"volatile-register",
|
||||||
@ -423,11 +309,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f4"
|
name = "stm32f4"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11460b4de3a84f072e2cf6e76306c64d27f405a0e83bace0a726f555ddf4bf33"
|
checksum = "44a3d6c58b14e63926273694e7dd644894513c5e35ce6928c4657ddb62cae976"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 0.2.5",
|
"bare-metal",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"vcell",
|
"vcell",
|
||||||
@ -435,112 +321,64 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32f4xx-hal"
|
name = "stm32f4xx-hal"
|
||||||
version = "0.8.3"
|
version = "0.7.0"
|
||||||
source = "git+https://github.com/stm32-rs/stm32f4xx-hal.git#e80925770d2fe72f0f01a7b46147f4e31d512689"
|
source = "git+https://github.com/thalesfragoso/stm32f4xx-hal?branch=pwm-impl#cfd073e094daa9be9dd2b0a1f859a4e1c6be2b77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 0.2.5",
|
"bare-metal",
|
||||||
"cast",
|
"cast",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"embedded-dma",
|
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
"nb 0.1.3",
|
"nb",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"stm32f4",
|
"stm32f4",
|
||||||
"synopsys-usb-otg",
|
|
||||||
"void",
|
"void",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.48"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "synopsys-usb-otg"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "461676dcf123675b3d3b02e2390e6a690cd186aacf2f439af7673c79e2561d53"
|
|
||||||
dependencies = [
|
|
||||||
"cortex-m",
|
|
||||||
"usb-device",
|
|
||||||
"vcell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thermostat"
|
name = "thermostat"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
"bare-metal",
|
||||||
"bit_field",
|
"bit_field",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-log",
|
"cortex-m-log",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"eeprom24x",
|
"hash2hwaddr",
|
||||||
"heapless",
|
|
||||||
"log",
|
"log",
|
||||||
"nb 1.0.0",
|
"nb",
|
||||||
"nom",
|
"nom",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"panic-abort",
|
"panic-abort",
|
||||||
"panic-semihosting",
|
"panic-semihosting",
|
||||||
"postcard",
|
|
||||||
"serde",
|
|
||||||
"serde-json-core",
|
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
"stm32-eth",
|
"stm32-eth",
|
||||||
"stm32f4xx-hal",
|
"stm32f4xx-hal",
|
||||||
"uom",
|
|
||||||
"usb-device",
|
|
||||||
"usbd-serial",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.12.0"
|
version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.1"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uom"
|
|
||||||
version = "0.30.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"serde",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "usb-device"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "849eed9b4dc61a1f17ba1d7a5078ceb095b9410caa38a506eb281ed5eff12fbd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "usbd-serial"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "db75519b86287f12dcf0d171c7cf4ecc839149fe9f3b720ac4cfce52959e1dfe"
|
|
||||||
dependencies = [
|
|
||||||
"embedded-hal",
|
|
||||||
"nb 0.1.3",
|
|
||||||
"usb-device",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcell"
|
name = "vcell"
|
||||||
@ -550,9 +388,9 @@ checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.2"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "void"
|
name = "void"
|
||||||
|
27
Cargo.toml
27
Cargo.toml
@ -14,36 +14,31 @@ features = []
|
|||||||
default-target = "thumbv7em-none-eabihf"
|
default-target = "thumbv7em-none-eabihf"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
panic-abort = "0.3"
|
panic-abort = "0.3.1"
|
||||||
panic-semihosting = { version = "0.5", optional = true }
|
panic-semihosting = { version = "0.5.1", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
bare-metal = "1"
|
bare-metal = "0.2"
|
||||||
cortex-m = "0.6"
|
cortex-m = "0.6"
|
||||||
cortex-m-rt = { version = "0.6", features = ["device"] }
|
cortex-m-rt = { version = "0.6", features = ["device"] }
|
||||||
cortex-m-log = { version = "0.6", features = ["log-integration"] }
|
cortex-m-log = { version = "0.6", features = ["log-integration"] }
|
||||||
stm32f4xx-hal = { version = "0.8", features = ["rt", "stm32f427", "usb_fs"] }
|
stm32f4xx-hal = { version = "0.7", features = ["rt", "stm32f427"] }
|
||||||
stm32-eth = { version = "0.2", features = ["stm32f427", "smoltcp-phy"], git = "https://github.com/stm32-rs/stm32-eth.git" }
|
stm32-eth = { version = "0.1.2", features = ["smoltcp-phy"], git = "https://github.com/stm32-rs/stm32-eth.git" }
|
||||||
smoltcp = { version = "0.6.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "log"] }
|
smoltcp = { version = "0.6.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "log"] }
|
||||||
|
hash2hwaddr = { version = "0.0", optional = true }
|
||||||
bit_field = "0.10"
|
bit_field = "0.10"
|
||||||
byteorder = { version = "1", default-features = false }
|
byteorder = { version = "1", default-features = false }
|
||||||
nom = { version = "5", default-features = false }
|
nom = { version = "5", default-features = false }
|
||||||
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
||||||
usb-device = "0.2"
|
nb = "0.1"
|
||||||
usbd-serial = "0.1"
|
|
||||||
nb = "1"
|
|
||||||
uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] }
|
|
||||||
eeprom24x = "0.3"
|
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
|
||||||
postcard = "0.5"
|
|
||||||
heapless = "0.5"
|
|
||||||
serde-json-core = "0.1"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" }
|
# TODO: pending https://github.com/stm32-rs/stm32f4xx-hal/pull/125
|
||||||
|
stm32f4xx-hal = { git = "https://github.com/thalesfragoso/stm32f4xx-hal", branch = "pwm-impl" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
||||||
|
generate-hwaddr = ["hash2hwaddr"]
|
||||||
|
default = ["generate-hwaddr"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
133
README.md
133
README.md
@ -52,13 +52,11 @@ The scope of this setting is per TCP session.
|
|||||||
| `report mode` | Show current report mode |
|
| `report mode` | Show current report mode |
|
||||||
| `report mode <off/on>` | Set report mode |
|
| `report mode <off/on>` | Set report mode |
|
||||||
| `pwm` | Show current PWM settings |
|
| `pwm` | Show current PWM settings |
|
||||||
| `pwm <0/1> max_i_pos <ratio>` | Set PWM duty cycle for **max_i_pos** to *ampere* |
|
| `pwm <0/1> max_i_pos <ratio>` | Set PWM duty cycle for **max_i_pos** to *ratio* |
|
||||||
| `pwm <0/1> max_i_neg <ratio>` | Set PWM duty cycle for **max_i_neg** to *ampere* |
|
| `pwm <0/1> max_i_neg <ratio>` | Set PWM duty cycle for **max_i_neg** to *ratio* |
|
||||||
| `pwm <0/1> max_v <ratio>` | Set PWM duty cycle for **max_v** to *volt* |
|
| `pwm <0/1> max_v <ratio>` | Set PWM duty cycle for **max_v** to *ratio* |
|
||||||
| `pwm <0/1> <volts>` | Disengage PID, set **i_set** DAC to *ampere* |
|
| `pwm <0/1> <volts>` | Disengage PID, set **i_set** DAC to *volts* |
|
||||||
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
|
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
|
||||||
| `center <0/1> <volts>` | Set the MAX1968 0A-centerpoint to *volts* |
|
|
||||||
| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
|
|
||||||
| `pid` | Show PID configuration |
|
| `pid` | Show PID configuration |
|
||||||
| `pid <0/1> target <value>` | Set the PID controller target |
|
| `pid <0/1> target <value>` | Set the PID controller target |
|
||||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||||
@ -69,126 +67,5 @@ The scope of this setting is per TCP session.
|
|||||||
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
||||||
| `pid <0/1> integral_max <value>` | Set integral upper bound |
|
| `pid <0/1> integral_max <value>` | Set integral upper bound |
|
||||||
| `s-h` | Show Steinhart-Hart equation parameters |
|
| `s-h` | Show Steinhart-Hart equation parameters |
|
||||||
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
| `s-h <0/1> <t/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
||||||
| `postfilter` | Show postfilter settings |
|
|
||||||
| `postfilter <0/1> off` | Disable postfilter |
|
|
||||||
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
|
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
|
||||||
| `load` | Restore configuration from EEPROM |
|
|
||||||
| `save` | Save configuration to EEPROM |
|
|
||||||
| `reset` | Reset the device |
|
|
||||||
| `ipv4 <X.X.X.X>` | Configure IPv4 address |
|
|
||||||
|
|
||||||
|
|
||||||
## USB
|
|
||||||
|
|
||||||
The firmware includes experimental support for acting as a USB-Serial
|
|
||||||
peripheral. Debug logging will be sent there by default (unless build
|
|
||||||
with logging via semihosting.)
|
|
||||||
|
|
||||||
**Caveat:** This logging does not flush its output. Doing so would
|
|
||||||
hang indefinitely if the output is not read by the USB host. Therefore
|
|
||||||
output will be truncated once buffers are full.
|
|
||||||
|
|
||||||
|
|
||||||
## Temperature measurement
|
|
||||||
|
|
||||||
Connect the thermistor with the SENS pins of the
|
|
||||||
device. Temperature-depending resistance is measured by the AD7172
|
|
||||||
ADC. To prepare conversion to a temperature, set the Beta parameters
|
|
||||||
for the Steinhart-Hart equation.
|
|
||||||
|
|
||||||
Set the base temperature in degrees celsius for the channel 0 thermistor:
|
|
||||||
```
|
|
||||||
s-h 0 t0 20
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the resistance in Ohms measured at the base temperature t0:
|
|
||||||
```
|
|
||||||
s-h 0 r0 10000
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the Beta parameter:
|
|
||||||
```
|
|
||||||
s-h 0 b 3800
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Thermo-Electric Cooling (TEC)
|
|
||||||
|
|
||||||
- Connect Peltier device 0 to TEC0- and TEC0+.
|
|
||||||
- Connect Peliter device 1 to TEC1- and TEC1+.
|
|
||||||
- The GND pin is for shielding not for sinking Peltier currents.
|
|
||||||
|
|
||||||
### Limits
|
|
||||||
|
|
||||||
Each of the MAX1968 TEC driver has analog/PWM inputs for setting
|
|
||||||
output limits.
|
|
||||||
|
|
||||||
Use the `pwm` command to see current settings and maximum values.
|
|
||||||
|
|
||||||
| Limit | Unit | Description |
|
|
||||||
| --- | :---: | --- |
|
|
||||||
| `max_v` | Volts | Maximum voltage |
|
|
||||||
| `max_i_pos` | Amperes | Maximum positive current |
|
|
||||||
| `max_i_neg` | Amperes | Maximum negative current |
|
|
||||||
| | Amperes | Output current control |
|
|
||||||
|
|
||||||
Example: set the maximum voltage of channel 0 to 1.5 V.
|
|
||||||
```
|
|
||||||
pwm 0 max_v 1.5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Open-loop mode
|
|
||||||
|
|
||||||
To manually control TEC output current, omit the limit parameter of
|
|
||||||
the `pwm` command. Doing so will disengage the PID control for that
|
|
||||||
channel.
|
|
||||||
|
|
||||||
Example: set output current of channel 0 to 0 A.
|
|
||||||
```
|
|
||||||
pwm 0 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## PID-stabilized temperature control
|
|
||||||
|
|
||||||
Set the target temperature of channel 0 to 20 degrees celsius:
|
|
||||||
```
|
|
||||||
pid 0 target 20
|
|
||||||
```
|
|
||||||
|
|
||||||
Enter closed-loop mode by switching control of the TEC output current
|
|
||||||
of channel 0 to the PID algorithm:
|
|
||||||
```
|
|
||||||
pwm 0 pid
|
|
||||||
```
|
|
||||||
|
|
||||||
## LED indicators
|
|
||||||
|
|
||||||
| Name | Color | Meaning |
|
|
||||||
| --- | :---: | --- |
|
|
||||||
| L1 | Red | Firmware initializing |
|
|
||||||
| L3 | Green | Closed-loop mode (PID engaged) |
|
|
||||||
| L4 | Green | Firmware busy |
|
|
||||||
|
|
||||||
## Reports
|
|
||||||
|
|
||||||
Use the bare `report` command to obtain a single report. Enable
|
|
||||||
continuous reporting with `report mode on`. Reports are JSON objects
|
|
||||||
with the following keys.
|
|
||||||
|
|
||||||
| Key | Unit | Description |
|
|
||||||
| --- | :---: | --- |
|
|
||||||
| `channel` | Integer | Channel `0`, or `1` |
|
|
||||||
| `time` | Milliseconds | Temperature measurement time |
|
|
||||||
| `adc` | Volts | AD7172 input |
|
|
||||||
| `sens` | Ohms | Thermistor resistance derived from `adc` |
|
|
||||||
| `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` |
|
|
||||||
| `pid_engaged` | Boolean | `true` if in closed-loop mode |
|
|
||||||
| `i_set` | Amperes | TEC output current |
|
|
||||||
| `vref` | Volts | MAX1968 VREF (1.5 V) |
|
|
||||||
| `dac_value` | Volts | AD5680 output derived from `i_set` |
|
|
||||||
| `dac_feedback` | Volts | ADC measurement of the AD5680 output |
|
|
||||||
| `i_tec` | Volts | MAX1968 TEC current monitor |
|
|
||||||
| `tec_i` | Amperes | TEC output current feedback derived from `i_tec` |
|
|
||||||
| `tec_u_meas` | Volts | Measurement of the voltage across the TEC |
|
|
||||||
| `pid_output` | Amperes | PID control output |
|
|
||||||
|
@ -1 +1 @@
|
|||||||
"055x3b3kqi7bi17ya6iaiq9hlsiy8f3v6bn47s6dizc6y4xn9v2y"
|
"0ma8dxsw90jrbxb3cd873k98g3pixnqvb059blvg7kf4m5aj9fnq"
|
||||||
|
124
pytec/plot.py
124
pytec/plot.py
@ -1,124 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import matplotlib.animation as animation
|
|
||||||
from threading import Thread, Lock
|
|
||||||
from pytec.client import Client
|
|
||||||
|
|
||||||
TIME_WINDOW = 300.0
|
|
||||||
|
|
||||||
tec = Client()
|
|
||||||
target_temperature = tec.get_pid()[0]['target']
|
|
||||||
print("Channel 0 target temperature: {:.3f}".format(target_temperature))
|
|
||||||
|
|
||||||
class Series:
|
|
||||||
def __init__(self, conv=lambda x: x):
|
|
||||||
self.conv = conv
|
|
||||||
self.x_data = []
|
|
||||||
self.y_data = []
|
|
||||||
|
|
||||||
def append(self, x, y):
|
|
||||||
self.x_data.append(x)
|
|
||||||
self.y_data.append(self.conv(y))
|
|
||||||
|
|
||||||
def clip(self, min_x):
|
|
||||||
drop = 0
|
|
||||||
while drop < len(self.x_data) and self.x_data[drop] < min_x:
|
|
||||||
drop += 1
|
|
||||||
self.x_data = self.x_data[drop:]
|
|
||||||
self.y_data = self.y_data[drop:]
|
|
||||||
|
|
||||||
series = {
|
|
||||||
'adc': Series(),
|
|
||||||
'sens': Series(lambda x: x * 0.0001),
|
|
||||||
'temperature': Series(lambda t: t - target_temperature),
|
|
||||||
'i_set': Series(),
|
|
||||||
'pid_output': Series(),
|
|
||||||
'vref': Series(),
|
|
||||||
'dac_value': Series(),
|
|
||||||
'dac_feedback': Series(),
|
|
||||||
'i_tec': Series(),
|
|
||||||
'tec_i': Series(),
|
|
||||||
'tec_u_meas': Series(),
|
|
||||||
}
|
|
||||||
series_lock = Lock()
|
|
||||||
|
|
||||||
quit = False
|
|
||||||
|
|
||||||
def recv_data(tec):
|
|
||||||
for data in tec.report_mode():
|
|
||||||
if data['channel'] == 0:
|
|
||||||
series_lock.acquire()
|
|
||||||
try:
|
|
||||||
time = data['time'] / 1000.0
|
|
||||||
for k, s in series.iteritems():
|
|
||||||
v = data[k]
|
|
||||||
if data.has_key(k) and type(v) is float:
|
|
||||||
s.append(time, v)
|
|
||||||
finally:
|
|
||||||
series_lock.release()
|
|
||||||
|
|
||||||
if quit:
|
|
||||||
break
|
|
||||||
|
|
||||||
thread = Thread(target=recv_data, args=(tec,))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
|
|
||||||
for k, s in series.iteritems():
|
|
||||||
s.plot, = ax.plot([], [], label=k)
|
|
||||||
legend = ax.legend()
|
|
||||||
|
|
||||||
def animate(i):
|
|
||||||
min_x, max_x, min_y, max_y = None, None, None, None
|
|
||||||
|
|
||||||
series_lock.acquire()
|
|
||||||
try:
|
|
||||||
for k, s in series.iteritems():
|
|
||||||
s.plot.set_data(s.x_data, s.y_data)
|
|
||||||
if len(s.y_data) > 0:
|
|
||||||
s.plot.set_label("{}: {:.3f}".format(k, s.y_data[-1]))
|
|
||||||
|
|
||||||
if len(s.x_data) > 0:
|
|
||||||
min_x_ = min(s.x_data)
|
|
||||||
if min_x is None:
|
|
||||||
min_x = min_x_
|
|
||||||
else:
|
|
||||||
min_x = min(min_x, min_x_)
|
|
||||||
max_x_ = max(s.x_data)
|
|
||||||
if max_x is None:
|
|
||||||
max_x = max_x_
|
|
||||||
else:
|
|
||||||
max_x = max(max_x, max_x_)
|
|
||||||
if len(s.y_data) > 0:
|
|
||||||
min_y_ = min(s.y_data)
|
|
||||||
if min_y is None:
|
|
||||||
min_y = min_y_
|
|
||||||
else:
|
|
||||||
min_y = min(min_y, min_y_)
|
|
||||||
max_y_ = max(s.y_data)
|
|
||||||
if max_y is None:
|
|
||||||
max_y = max_y_
|
|
||||||
else:
|
|
||||||
max_y = max(max_y, max_y_)
|
|
||||||
|
|
||||||
if min_x is not None and max_x - TIME_WINDOW > min_x:
|
|
||||||
for s in series.itervalues():
|
|
||||||
s.clip(max_x - TIME_WINDOW)
|
|
||||||
finally:
|
|
||||||
series_lock.release()
|
|
||||||
|
|
||||||
margin_y = 0.01 * (max_y - min_y)
|
|
||||||
ax.set_xlim(min_x, max_x)
|
|
||||||
ax.set_ylim(min_y - margin_y, max_y + margin_y)
|
|
||||||
|
|
||||||
global legend
|
|
||||||
legend.remove()
|
|
||||||
legend = ax.legend()
|
|
||||||
|
|
||||||
ani = animation.FuncAnimation(
|
|
||||||
fig, animate, interval=1, blit=False, save_count=50)
|
|
||||||
|
|
||||||
plt.show()
|
|
||||||
quit = True
|
|
||||||
thread.join()
|
|
@ -1,163 +0,0 @@
|
|||||||
import socket
|
|
||||||
import json
|
|
||||||
|
|
||||||
CHANNELS = 2
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
def __init__(self, host="192.168.1.26", port=23, timeout=None):
|
|
||||||
self._socket = socket.create_connection((host, port), timeout)
|
|
||||||
self._lines = [""]
|
|
||||||
|
|
||||||
def _command(self, *command):
|
|
||||||
self._socket.sendall((" ".join(command) + "\n").encode('utf-8'))
|
|
||||||
|
|
||||||
def _read_line(self):
|
|
||||||
# read more lines
|
|
||||||
while len(self._lines) <= 1:
|
|
||||||
chunk = self._socket.recv(4096)
|
|
||||||
if not chunk:
|
|
||||||
return None
|
|
||||||
buf = self._lines[-1] + chunk.decode('utf-8', errors='ignore')
|
|
||||||
self._lines = buf.split("\n")
|
|
||||||
|
|
||||||
line = self._lines[0]
|
|
||||||
self._lines = self._lines[1:]
|
|
||||||
return line
|
|
||||||
|
|
||||||
def _get_conf(self, topic):
|
|
||||||
self._command(topic)
|
|
||||||
result = []
|
|
||||||
for channel in range(0, CHANNELS):
|
|
||||||
line = self._read_line()
|
|
||||||
conf = json.loads(line)
|
|
||||||
result.append(conf)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_pwm(self):
|
|
||||||
"""Retrieve PWM limits for the TEC
|
|
||||||
|
|
||||||
Example::
|
|
||||||
[{'channel': 0,
|
|
||||||
'center': 'vref',
|
|
||||||
'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762},
|
|
||||||
'max_i_neg': {'max': 3.0, 'value': 3.0},
|
|
||||||
'max_v': {'max': 5.988, 'value': 5.988},
|
|
||||||
'max_i_pos': {'max': 3.0, 'value': 3.0}},
|
|
||||||
{'channel': 1,
|
|
||||||
'center': 'vref',
|
|
||||||
'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762},
|
|
||||||
'max_i_neg': {'max': 3.0, 'value': 3.0},
|
|
||||||
'max_v': {'max': 5.988, 'value': 5.988},
|
|
||||||
'max_i_pos': {'max': 3.0, 'value': 3.0}}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
return self._get_conf("pwm")
|
|
||||||
|
|
||||||
def get_pid(self):
|
|
||||||
"""Retrieve PID control state
|
|
||||||
|
|
||||||
Example::
|
|
||||||
[{'channel': 0,
|
|
||||||
'parameters': {
|
|
||||||
'kp': 10.0,
|
|
||||||
'ki': 0.02,
|
|
||||||
'kd': 0.0,
|
|
||||||
'output_min': 0.0,
|
|
||||||
'output_max': 3.0,
|
|
||||||
'integral_min': -100.0,
|
|
||||||
'integral_max': 100.0},
|
|
||||||
'target': 37.0,
|
|
||||||
'integral': 38.41138597026372},
|
|
||||||
{'channel': 1,
|
|
||||||
'parameters': {
|
|
||||||
'kp': 10.0,
|
|
||||||
'ki': 0.02,
|
|
||||||
'kd': 0.0,
|
|
||||||
'output_min': 0.0,
|
|
||||||
'output_max': 3.0,
|
|
||||||
'integral_min': -100.0,
|
|
||||||
'integral_max': 100.0},
|
|
||||||
'target': 36.5,
|
|
||||||
'integral': nan}]
|
|
||||||
"""
|
|
||||||
return self._get_conf("pid")
|
|
||||||
|
|
||||||
def get_steinhart_hart(self):
|
|
||||||
"""Retrieve Steinhart-Hart parameters for resistance to temperature conversion
|
|
||||||
|
|
||||||
Example::
|
|
||||||
[{'params': {'b': 3800.0, 'r0': 10000.0, 't0': 298.15}, 'channel': 0},
|
|
||||||
{'params': {'b': 3800.0, 'r0': 10000.0, 't0': 298.15}, 'channel': 1}]
|
|
||||||
"""
|
|
||||||
return self._get_conf("s-h")
|
|
||||||
|
|
||||||
def get_postfilter(self):
|
|
||||||
"""Retrieve DAC postfilter configuration
|
|
||||||
|
|
||||||
Example::
|
|
||||||
[{'rate': None, 'channel': 0},
|
|
||||||
{'rate': 21.25, 'channel': 1}]
|
|
||||||
"""
|
|
||||||
return self._get_conf("postfilter")
|
|
||||||
|
|
||||||
def report_mode(self):
|
|
||||||
"""Start reporting measurement values
|
|
||||||
|
|
||||||
Example of yielded data::
|
|
||||||
{'channel': 0,
|
|
||||||
'time': 2302524,
|
|
||||||
'adc': 0.6199188965423515,
|
|
||||||
'sens': 6138.519310282602,
|
|
||||||
'temperature': 36.87032392655527,
|
|
||||||
'pid_engaged': True,
|
|
||||||
'i_set': 2.0635816680889123,
|
|
||||||
'vref': 1.494,
|
|
||||||
'dac_value': 2.527790834044456,
|
|
||||||
'dac_feedback': 2.523,
|
|
||||||
'i_tec': 2.331,
|
|
||||||
'tec_i': 2.0925,
|
|
||||||
'tec_u_meas': 2.5340000000000003,
|
|
||||||
'pid_output': 2.067581958092247}
|
|
||||||
"""
|
|
||||||
self._command("report mode", "on")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
line = self._read_line()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
yield json.loads(line)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_param(self, topic, channel, field="", value=""):
|
|
||||||
"""Set configuration parameters
|
|
||||||
|
|
||||||
Examples::
|
|
||||||
tec.set_param("pwm", 0, "max_v", 2.0)
|
|
||||||
tec.set_param("pid", 1, "output_max", 2.5)
|
|
||||||
tec.set_param("s-h", 0, "t0", 20.0)
|
|
||||||
tec.set_param("center", 0, "vref")
|
|
||||||
tec.set_param("postfilter", 1, 21)
|
|
||||||
|
|
||||||
See the firmware's README.md for a full list.
|
|
||||||
"""
|
|
||||||
if type(value) is float:
|
|
||||||
value = "{:f}".format(value)
|
|
||||||
if type(value) is not str:
|
|
||||||
value = str(value)
|
|
||||||
self._command(topic, str(channel), field, value)
|
|
||||||
|
|
||||||
def power_up(self, channel, target):
|
|
||||||
"""Start closed-loop mode"""
|
|
||||||
self.set_param("pid", channel, "target", value=target)
|
|
||||||
self.set_param("pwm", channel, "pid")
|
|
||||||
|
|
||||||
def save_config(self):
|
|
||||||
"""Save current configuration to EEPROM"""
|
|
||||||
self._command("save")
|
|
||||||
|
|
||||||
def load_config(self):
|
|
||||||
"""Load current configuration from EEPROM"""
|
|
||||||
self._command("load")
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
from setuptools import setup, find_packages
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="pytec",
|
|
||||||
version="0.0",
|
|
||||||
author="M-Labs",
|
|
||||||
url="https://git.m-labs.hk/M-Labs/thermostat",
|
|
||||||
description="Control TEC",
|
|
||||||
license="GPLv3",
|
|
||||||
install_requires=["setuptools"],
|
|
||||||
packages=find_packages(),
|
|
||||||
)
|
|
@ -1,8 +0,0 @@
|
|||||||
from pytec.client import Client
|
|
||||||
|
|
||||||
tec = Client() #(host="localhost", port=6667)
|
|
||||||
tec.set_param("s-h", 1, "t0", 20)
|
|
||||||
print(tec.get_pid())
|
|
||||||
print(tec.get_steinhart_hart())
|
|
||||||
for data in tec.report_mode():
|
|
||||||
print(data)
|
|
@ -6,7 +6,6 @@ use stm32f4xx_hal::{
|
|||||||
time::MegaHertz,
|
time::MegaHertz,
|
||||||
spi,
|
spi,
|
||||||
};
|
};
|
||||||
use crate::timer::sleep;
|
|
||||||
|
|
||||||
/// SPI Mode 1
|
/// SPI Mode 1
|
||||||
pub const SPI_MODE: spi::Mode = spi::Mode {
|
pub const SPI_MODE: spi::Mode = spi::Mode {
|
||||||
@ -34,25 +33,23 @@ impl<SPI: Transfer<u8>, S: OutputPin> Dac<SPI, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, buf: &mut [u8]) -> Result<(), SPI::Error> {
|
fn write(&mut self, mut buf: [u8; 3]) -> Result<(), SPI::Error> {
|
||||||
// pulse sync to start a new transfer. leave sync idle low
|
// pulse sync to start a new transfer. leave sync idle low
|
||||||
// afterwards to save power as recommended per datasheet.
|
// afterwards to save power as recommended per datasheet.
|
||||||
let _ = self.sync.set_high();
|
let _ = self.sync.set_high();
|
||||||
// must be high for >= 33 ns
|
cortex_m::asm::nop();
|
||||||
sleep(1);
|
|
||||||
let _ = self.sync.set_low();
|
let _ = self.sync.set_low();
|
||||||
self.spi.transfer(buf)?;
|
|
||||||
|
self.spi.transfer(&mut buf)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, value: u32) -> Result<u32, SPI::Error> {
|
pub fn set(&mut self, value: u32) -> Result<(), SPI::Error> {
|
||||||
let value = value.min(MAX_VALUE);
|
let buf = [
|
||||||
let mut buf = [
|
|
||||||
(value >> 14) as u8,
|
(value >> 14) as u8,
|
||||||
(value >> 6) as u8,
|
(value >> 6) as u8,
|
||||||
(value << 2) as u8,
|
(value << 2) as u8,
|
||||||
];
|
];
|
||||||
self.write(&mut buf)?;
|
self.write(buf)
|
||||||
Ok(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,6 @@ use stm32f4xx_hal::hal::{
|
|||||||
blocking::spi::Transfer,
|
blocking::spi::Transfer,
|
||||||
digital::v2::OutputPin,
|
digital::v2::OutputPin,
|
||||||
};
|
};
|
||||||
use uom::si::{
|
|
||||||
f64::ElectricPotential,
|
|
||||||
electric_potential::volt,
|
|
||||||
};
|
|
||||||
use super::{
|
use super::{
|
||||||
regs::{self, Register, RegisterData},
|
regs::{self, Register, RegisterData},
|
||||||
checksum::{ChecksumMode, Checksum},
|
checksum::{ChecksumMode, Checksum},
|
||||||
@ -90,8 +86,6 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||||||
data.set_enh_filt_en(true);
|
data.set_enh_filt_en(true);
|
||||||
data.set_enh_filt(PostFilter::F16SPS);
|
data.set_enh_filt(PostFilter::F16SPS);
|
||||||
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
||||||
// output data rate: 10 Hz
|
|
||||||
data.set_odr(0b10011);
|
|
||||||
})?;
|
})?;
|
||||||
self.update_reg(®s::Channel { index }, |data| {
|
self.update_reg(®s::Channel { index }, |data| {
|
||||||
data.set_setup(index);
|
data.set_setup(index);
|
||||||
@ -102,11 +96,45 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> {
|
pub fn disable_channel(
|
||||||
let offset = self.read_reg(®s::Offset { index })?.offset();
|
&mut self, index: u8
|
||||||
let gain = self.read_reg(®s::Gain { index })?.gain();
|
) -> Result<(), SPI::Error> {
|
||||||
let bipolar = self.read_reg(®s::SetupCon { index })?.bipolar();
|
self.update_reg(®s::Channel { index }, |data| {
|
||||||
Ok(ChannelCalibration { offset, gain, bipolar })
|
data.set_enabled(false);
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_all_channels(&mut self) -> Result<(), SPI::Error> {
|
||||||
|
for index in 0..4 {
|
||||||
|
self.update_reg(®s::Channel { index }, |data| {
|
||||||
|
data.set_enabled(false);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calibrates offset registers
|
||||||
|
pub fn calibrate(&mut self) -> Result<(), SPI::Error> {
|
||||||
|
// internal offset calibration
|
||||||
|
self.update_reg(®s::AdcMode, |adc_mode| {
|
||||||
|
adc_mode.set_mode(Mode::InternalOffsetCalibration);
|
||||||
|
})?;
|
||||||
|
while ! self.read_reg(®s::Status)?.ready() {}
|
||||||
|
|
||||||
|
// system offset calibration
|
||||||
|
self.update_reg(®s::AdcMode, |adc_mode| {
|
||||||
|
adc_mode.set_mode(Mode::SystemOffsetCalibration);
|
||||||
|
})?;
|
||||||
|
while ! self.read_reg(®s::Status)?.ready() {}
|
||||||
|
|
||||||
|
// system gain calibration
|
||||||
|
self.update_reg(®s::AdcMode, |adc_mode| {
|
||||||
|
adc_mode.set_mode(Mode::SystemGainCalibration);
|
||||||
|
})?;
|
||||||
|
while ! self.read_reg(®s::Status)?.ready() {}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_continuous_conversion(&mut self) -> Result<(), SPI::Error> {
|
pub fn start_continuous_conversion(&mut self) -> Result<(), SPI::Error> {
|
||||||
@ -234,7 +262,7 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
};
|
};
|
||||||
let result = match (result, checksum) {
|
let result = match (result, checksum) {
|
||||||
(Ok(_), None) =>
|
(Ok(_),None) =>
|
||||||
Ok(None),
|
Ok(None),
|
||||||
(Ok(_), Some(checksum_out)) => {
|
(Ok(_), Some(checksum_out)) => {
|
||||||
let mut checksum_buf = [checksum_out; 1];
|
let mut checksum_buf = [checksum_out; 1];
|
||||||
@ -251,26 +279,3 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ChannelCalibration {
|
|
||||||
offset: u32,
|
|
||||||
gain: u32,
|
|
||||||
bipolar: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelCalibration {
|
|
||||||
pub fn convert_data(&self, data: u32) -> ElectricPotential {
|
|
||||||
let data = if self.bipolar {
|
|
||||||
(data as i32 - 0x80_0000) as f64
|
|
||||||
} else {
|
|
||||||
data as f64 / 2.0
|
|
||||||
};
|
|
||||||
let data = data / (self.gain as f64 / (0x40_0000 as f64));
|
|
||||||
let data = data + (self.offset as i32 - 0x80_0000) as f64;
|
|
||||||
let data = data / (2 << 23) as f64;
|
|
||||||
|
|
||||||
const V_REF: f64 = 3.3;
|
|
||||||
ElectricPotential::new::<volt>(data * V_REF / 0.75)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use stm32f4xx_hal::{
|
use stm32f4xx_hal::{
|
||||||
time::MegaHertz,
|
time::MegaHertz,
|
||||||
spi,
|
spi,
|
||||||
@ -145,7 +144,7 @@ impl fmt::Display for RefSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum PostFilter {
|
pub enum PostFilter {
|
||||||
/// 27 SPS, 47 dB rejection, 36.7 ms settling
|
/// 27 SPS, 47 dB rejection, 36.7 ms settling
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use stm32f4xx_hal::hal::digital::v2::OutputPin;
|
use stm32f4xx_hal::hal::digital::v2::OutputPin;
|
||||||
use crate::{
|
use crate::{
|
||||||
ad5680,
|
ad5680,
|
||||||
ad7172,
|
|
||||||
channel_state::ChannelState,
|
channel_state::ChannelState,
|
||||||
pins::{ChannelPins, ChannelPinSet},
|
pins::{ChannelPins, ChannelPinSet},
|
||||||
};
|
};
|
||||||
@ -20,6 +19,8 @@ pub struct Channel<C: ChannelPins> {
|
|||||||
/// 1 / Volts
|
/// 1 / Volts
|
||||||
pub dac_factor: f64,
|
pub dac_factor: f64,
|
||||||
pub shdn: C::Shdn,
|
pub shdn: C::Shdn,
|
||||||
|
/// stm32f4 integrated adc
|
||||||
|
pub adc: C::Adc,
|
||||||
pub vref_pin: C::VRefPin,
|
pub vref_pin: C::VRefPin,
|
||||||
pub itec_pin: C::ItecPin,
|
pub itec_pin: C::ItecPin,
|
||||||
/// feedback from `dac` output
|
/// feedback from `dac` output
|
||||||
@ -28,10 +29,12 @@ pub struct Channel<C: ChannelPins> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<C: ChannelPins> Channel<C> {
|
impl<C: ChannelPins> Channel<C> {
|
||||||
pub fn new(pins: ChannelPinSet<C>, adc_calibration: ad7172::ChannelCalibration) -> Self {
|
pub fn new(mut pins: ChannelPinSet<C>) -> Self {
|
||||||
let state = ChannelState::new(adc_calibration);
|
let state = ChannelState::default();
|
||||||
let mut dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync);
|
let mut dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync);
|
||||||
let _ = dac.set(0);
|
let _ = dac.set(0);
|
||||||
|
// power up TEC
|
||||||
|
let _ = pins.shdn.set_high();
|
||||||
// sensible dummy preset. calibrate_i_set() must be used.
|
// sensible dummy preset. calibrate_i_set() must be used.
|
||||||
let dac_factor = ad5680::MAX_VALUE as f64 / 5.0;
|
let dac_factor = ad5680::MAX_VALUE as f64 / 5.0;
|
||||||
|
|
||||||
@ -39,20 +42,11 @@ impl<C: ChannelPins> Channel<C> {
|
|||||||
state,
|
state,
|
||||||
dac, dac_factor,
|
dac, dac_factor,
|
||||||
shdn: pins.shdn,
|
shdn: pins.shdn,
|
||||||
|
adc: pins.adc,
|
||||||
vref_pin: pins.vref_pin,
|
vref_pin: pins.vref_pin,
|
||||||
itec_pin: pins.itec_pin,
|
itec_pin: pins.itec_pin,
|
||||||
dac_feedback_pin: pins.dac_feedback_pin,
|
dac_feedback_pin: pins.dac_feedback_pin,
|
||||||
tec_u_meas_pin: pins.tec_u_meas_pin,
|
tec_u_meas_pin: pins.tec_u_meas_pin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// power up TEC
|
|
||||||
pub fn power_up(&mut self) {
|
|
||||||
let _ = self.shdn.set_high();
|
|
||||||
}
|
|
||||||
|
|
||||||
// power down TEC
|
|
||||||
pub fn power_down(&mut self) {
|
|
||||||
let _ = self.shdn.set_low();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,88 +1,43 @@
|
|||||||
use smoltcp::time::Instant;
|
use smoltcp::time::Instant;
|
||||||
use uom::si::{
|
|
||||||
f64::{
|
|
||||||
ElectricPotential,
|
|
||||||
ElectricalResistance,
|
|
||||||
ThermodynamicTemperature,
|
|
||||||
},
|
|
||||||
electric_potential::volt,
|
|
||||||
electrical_resistance::ohm,
|
|
||||||
thermodynamic_temperature::degree_celsius,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ad7172,
|
ad7172,
|
||||||
pid,
|
pid,
|
||||||
steinhart_hart as sh,
|
steinhart_hart as sh,
|
||||||
command_parser::CenterPoint,
|
units::Volts,
|
||||||
};
|
};
|
||||||
|
|
||||||
const R_INNER: f64 = 2.0 * 5100.0;
|
|
||||||
const VREF_SENS: f64 = 3.3 / 2.0;
|
|
||||||
|
|
||||||
pub struct ChannelState {
|
pub struct ChannelState {
|
||||||
pub adc_data: Option<u32>,
|
pub adc_data: Option<u32>,
|
||||||
pub adc_calibration: ad7172::ChannelCalibration,
|
|
||||||
pub adc_time: Instant,
|
pub adc_time: Instant,
|
||||||
/// VREF for the TEC (1.5V)
|
pub dac_value: Volts,
|
||||||
pub vref: ElectricPotential,
|
|
||||||
/// i_set 0A center point
|
|
||||||
pub center: CenterPoint,
|
|
||||||
pub dac_value: ElectricPotential,
|
|
||||||
pub pid_engaged: bool,
|
pub pid_engaged: bool,
|
||||||
pub pid: pid::Controller,
|
pub pid: pid::Controller,
|
||||||
pub sh: sh::Parameters,
|
pub sh: sh::Parameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelState {
|
impl Default for ChannelState {
|
||||||
pub fn new(adc_calibration: ad7172::ChannelCalibration) -> Self {
|
fn default() -> Self {
|
||||||
ChannelState {
|
ChannelState {
|
||||||
adc_data: None,
|
adc_data: None,
|
||||||
adc_calibration,
|
|
||||||
adc_time: Instant::from_secs(0),
|
adc_time: Instant::from_secs(0),
|
||||||
// updated later with Channels.read_vref()
|
dac_value: Volts(0.0),
|
||||||
vref: ElectricPotential::new::<volt>(1.5),
|
|
||||||
center: CenterPoint::Vref,
|
|
||||||
dac_value: ElectricPotential::new::<volt>(0.0),
|
|
||||||
pid_engaged: false,
|
pid_engaged: false,
|
||||||
pid: pid::Controller::new(pid::Parameters::default()),
|
pid: pid::Controller::new(pid::Parameters::default()),
|
||||||
sh: sh::Parameters::default(),
|
sh: sh::Parameters::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, now: Instant, adc_data: u32) {
|
impl ChannelState {
|
||||||
self.adc_data = if adc_data == ad7172::MAX_VALUE {
|
|
||||||
// this means there is no thermistor plugged into the ADC.
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(adc_data)
|
|
||||||
};
|
|
||||||
self.adc_time = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update PID state on ADC input, calculate new DAC output
|
/// Update PID state on ADC input, calculate new DAC output
|
||||||
pub fn update_pid(&mut self) -> Option<f64> {
|
pub fn update_pid(&mut self, now: Instant, adc_data: u32) -> f64 {
|
||||||
let temperature = self.get_temperature()?
|
self.adc_data = Some(adc_data);
|
||||||
.get::<degree_celsius>();
|
self.adc_time = now;
|
||||||
let pid_output = self.pid.update(temperature);
|
|
||||||
Some(pid_output)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
// Update PID controller
|
||||||
Some(self.adc_calibration.convert_data(self.adc_data?))
|
let input = (adc_data as f64) / (ad7172::MAX_VALUE as f64);
|
||||||
}
|
let temperature = self.sh.get_temperature(input);
|
||||||
|
self.pid.update(temperature)
|
||||||
/// Get `SENS[01]` input resistance
|
|
||||||
pub fn get_sens(&self) -> Option<ElectricalResistance> {
|
|
||||||
let r_inner = ElectricalResistance::new::<ohm>(R_INNER);
|
|
||||||
let vref = ElectricPotential::new::<volt>(VREF_SENS);
|
|
||||||
let adc_input = self.get_adc()?;
|
|
||||||
let r = r_inner * adc_input / (vref - adc_input);
|
|
||||||
Some(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_temperature(&self) -> Option<ThermodynamicTemperature> {
|
|
||||||
let r = self.get_sens()?;
|
|
||||||
let temperature = self.sh.get_temperature(r);
|
|
||||||
Some(temperature)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
576
src/channels.rs
576
src/channels.rs
@ -1,63 +1,53 @@
|
|||||||
use serde::{Serialize, Serializer};
|
|
||||||
use smoltcp::time::Instant;
|
use smoltcp::time::Instant;
|
||||||
use stm32f4xx_hal::hal;
|
use log::info;
|
||||||
use uom::si::{
|
|
||||||
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
|
||||||
electric_potential::{millivolt, volt},
|
|
||||||
electric_current::ampere,
|
|
||||||
electrical_resistance::ohm,
|
|
||||||
ratio::ratio,
|
|
||||||
thermodynamic_temperature::degree_celsius,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ad5680,
|
ad5680,
|
||||||
ad7172,
|
ad7172,
|
||||||
channel::{Channel, Channel0, Channel1},
|
channel::{Channel, Channel0, Channel1},
|
||||||
channel_state::ChannelState,
|
channel_state::ChannelState,
|
||||||
command_parser::{CenterPoint, PwmPin},
|
|
||||||
pins,
|
pins,
|
||||||
steinhart_hart,
|
units::Volts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CHANNELS: usize = 2;
|
pub const CHANNELS: usize = 2;
|
||||||
pub const R_SENSE: f64 = 0.05;
|
|
||||||
|
|
||||||
// TODO: -pub
|
// TODO: -pub
|
||||||
pub struct Channels {
|
pub struct Channels {
|
||||||
channel0: Channel<Channel0>,
|
channel0: Channel<Channel0>,
|
||||||
channel1: Channel<Channel1>,
|
channel1: Channel<Channel1>,
|
||||||
pub adc: ad7172::Adc<pins::AdcSpi, pins::AdcNss>,
|
pub adc: ad7172::Adc<pins::AdcSpi, pins::AdcNss>,
|
||||||
/// stm32f4 integrated adc
|
tec_u_meas_adc: pins::TecUMeasAdc,
|
||||||
pins_adc: pins::PinsAdc,
|
|
||||||
pub pwm: pins::PwmPins,
|
pub pwm: pins::PwmPins,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channels {
|
impl Channels {
|
||||||
pub fn new(pins: pins::Pins) -> Self {
|
pub fn new(pins: pins::Pins) -> Self {
|
||||||
|
let channel0 = Channel::new(pins.channel0);
|
||||||
|
let channel1 = Channel::new(pins.channel1);
|
||||||
|
let tec_u_meas_adc = pins.tec_u_meas_adc;
|
||||||
|
let pwm = pins.pwm;
|
||||||
|
|
||||||
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
|
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
|
||||||
// Feature not used
|
// Feature not used
|
||||||
adc.set_sync_enable(false).unwrap();
|
adc.set_sync_enable(false).unwrap();
|
||||||
|
|
||||||
|
// Calibrate ADC channels individually
|
||||||
|
adc.disable_all_channels().unwrap();
|
||||||
|
|
||||||
|
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
|
||||||
|
adc.calibrate().unwrap();
|
||||||
|
adc.disable_channel(0).unwrap();
|
||||||
|
|
||||||
|
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
|
||||||
|
adc.calibrate().unwrap();
|
||||||
|
adc.disable_channel(1).unwrap();
|
||||||
|
|
||||||
// Setup channels and start ADC
|
// Setup channels and start ADC
|
||||||
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
|
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
|
||||||
let adc_calibration0 = adc.get_calibration(0)
|
|
||||||
.expect("adc_calibration0");
|
|
||||||
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
|
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
|
||||||
let adc_calibration1 = adc.get_calibration(1)
|
|
||||||
.expect("adc_calibration1");
|
|
||||||
adc.start_continuous_conversion().unwrap();
|
adc.start_continuous_conversion().unwrap();
|
||||||
|
|
||||||
let channel0 = Channel::new(pins.channel0, adc_calibration0);
|
Channels { channel0, channel1, adc, tec_u_meas_adc, pwm }
|
||||||
let channel1 = Channel::new(pins.channel1, adc_calibration1);
|
|
||||||
let pins_adc = pins.pins_adc;
|
|
||||||
let pwm = pins.pwm;
|
|
||||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
|
|
||||||
for channel in 0..CHANNELS {
|
|
||||||
channels.channel_state(channel).vref = channels.read_vref(channel);
|
|
||||||
channels.calibrate_dac_value(channel);
|
|
||||||
channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0));
|
|
||||||
}
|
|
||||||
channels
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_state<I: Into<usize>>(&mut self, channel: I) -> &mut ChannelState {
|
pub fn channel_state<I: Into<usize>>(&mut self, channel: I) -> &mut ChannelState {
|
||||||
@ -73,218 +63,154 @@ impl Channels {
|
|||||||
self.adc.data_ready().unwrap().map(|channel| {
|
self.adc.data_ready().unwrap().map(|channel| {
|
||||||
let data = self.adc.read_data().unwrap();
|
let data = self.adc.read_data().unwrap();
|
||||||
|
|
||||||
|
let dac_value = {
|
||||||
let state = self.channel_state(channel);
|
let state = self.channel_state(channel);
|
||||||
state.update(instant, data);
|
let pid_output = state.update_pid(instant, data);
|
||||||
match state.update_pid() {
|
|
||||||
Some(pid_output) if state.pid_engaged => {
|
if state.pid_engaged {
|
||||||
|
Some(pid_output)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(dac_value) = dac_value {
|
||||||
// Forward PID output to i_set DAC
|
// Forward PID output to i_set DAC
|
||||||
self.set_i(channel.into(), ElectricCurrent::new::<ampere>(pid_output));
|
self.set_dac(channel.into(), Volts(dac_value));
|
||||||
self.power_up(channel);
|
|
||||||
}
|
|
||||||
None if state.pid_engaged => {
|
|
||||||
self.power_down(channel);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel
|
channel
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// calculate the TEC i_set centerpoint
|
|
||||||
pub fn get_center(&mut self, channel: usize) -> ElectricPotential {
|
|
||||||
match self.channel_state(channel).center {
|
|
||||||
CenterPoint::Vref => {
|
|
||||||
let vref = self.read_vref(channel);
|
|
||||||
self.channel_state(channel).vref = vref;
|
|
||||||
vref
|
|
||||||
},
|
|
||||||
CenterPoint::Override(center_point) =>
|
|
||||||
ElectricPotential::new::<volt>(center_point.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// i_set DAC
|
/// i_set DAC
|
||||||
fn get_dac(&mut self, channel: usize) -> (ElectricPotential, ElectricPotential) {
|
pub fn set_dac(&mut self, channel: usize, voltage: Volts) {
|
||||||
let dac_factor = match channel.into() {
|
let dac_factor = match channel.into() {
|
||||||
0 => self.channel0.dac_factor,
|
0 => self.channel0.dac_factor,
|
||||||
1 => self.channel1.dac_factor,
|
1 => self.channel1.dac_factor,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let voltage = self.channel_state(channel).dac_value;
|
let value = (voltage.0 * dac_factor) as u32;
|
||||||
let max = ElectricPotential::new::<volt>(ad5680::MAX_VALUE as f64 / dac_factor);
|
|
||||||
(voltage, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_i(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let center_point = self.get_center(channel);
|
|
||||||
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
|
|
||||||
let (voltage, max) = self.get_dac(channel);
|
|
||||||
let i_tec = (voltage - center_point) / (10.0 * r_sense);
|
|
||||||
let max = (max - center_point) / (10.0 * r_sense);
|
|
||||||
(i_tec, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// i_set DAC
|
|
||||||
fn set_dac(&mut self, channel: usize, voltage: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
|
|
||||||
let dac_factor = match channel.into() {
|
|
||||||
0 => self.channel0.dac_factor,
|
|
||||||
1 => self.channel1.dac_factor,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let value = (voltage.get::<volt>() * dac_factor) as u32;
|
|
||||||
let value = match channel {
|
|
||||||
0 => self.channel0.dac.set(value).unwrap(),
|
|
||||||
1 => self.channel1.dac.set(value).unwrap(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let voltage = ElectricPotential::new::<volt>(value as f64 / dac_factor);
|
|
||||||
self.channel_state(channel).dac_value = voltage;
|
|
||||||
let max = ElectricPotential::new::<volt>(ad5680::MAX_VALUE as f64 / dac_factor);
|
|
||||||
(voltage, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_i(&mut self, channel: usize, i_tec: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let center_point = self.get_center(channel);
|
|
||||||
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
|
|
||||||
let voltage = i_tec * 10.0 * r_sense + center_point;
|
|
||||||
let (voltage, max) = self.set_dac(channel, voltage);
|
|
||||||
let i_tec = (voltage - center_point) / (10.0 * r_sense);
|
|
||||||
let max = (max - center_point) / (10.0 * r_sense);
|
|
||||||
(i_tec, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_dac_feedback(&mut self, channel: usize) -> ElectricPotential {
|
|
||||||
match channel {
|
match channel {
|
||||||
0 => {
|
0 => {
|
||||||
let sample = self.pins_adc.convert(
|
self.channel0.dac.set(value).unwrap();
|
||||||
|
self.channel0.state.dac_value = voltage;
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
self.channel1.dac.set(value).unwrap();
|
||||||
|
self.channel1.state.dac_value = voltage;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_dac_feedback(&mut self, channel: usize) -> Volts {
|
||||||
|
match channel {
|
||||||
|
0 => {
|
||||||
|
let sample = self.channel0.adc.convert(
|
||||||
&self.channel0.dac_feedback_pin,
|
&self.channel0.dac_feedback_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel0.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.channel1.adc.convert(
|
||||||
&self.channel1.dac_feedback_pin,
|
&self.channel1.dac_feedback_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel1.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
|
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: f64) -> Volts {
|
||||||
let mut prev = self.read_dac_feedback(channel);
|
let mut prev = self.read_dac_feedback(channel);
|
||||||
loop {
|
loop {
|
||||||
let current = self.read_dac_feedback(channel);
|
let current = self.read_dac_feedback(channel);
|
||||||
if (current - prev).abs() < tolerance {
|
use num_traits::float::Float;
|
||||||
|
if (current - prev).0.abs() < tolerance {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
prev = current;
|
prev = current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_itec(&mut self, channel: usize) -> ElectricPotential {
|
pub fn read_itec(&mut self, channel: usize) -> Volts {
|
||||||
match channel {
|
match channel {
|
||||||
0 => {
|
0 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.channel0.adc.convert(
|
||||||
&self.channel0.itec_pin,
|
&self.channel0.itec_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel0.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.channel1.adc.convert(
|
||||||
&self.channel1.itec_pin,
|
&self.channel1.itec_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel1.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// should be 1.5V
|
/// should be 1.5V
|
||||||
pub fn read_vref(&mut self, channel: usize) -> ElectricPotential {
|
pub fn read_vref(&mut self, channel: usize) -> Volts {
|
||||||
match channel {
|
match channel {
|
||||||
0 => {
|
0 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.channel0.adc.convert(
|
||||||
&self.channel0.vref_pin,
|
&self.channel0.vref_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel0.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.channel1.adc.convert(
|
||||||
&self.channel1.vref_pin,
|
&self.channel1.vref_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.channel1.adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_tec_u_meas(&mut self, channel: usize) -> ElectricPotential {
|
pub fn read_tec_u_meas(&mut self, channel: usize) -> Volts {
|
||||||
match channel {
|
match channel {
|
||||||
0 => {
|
0 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.tec_u_meas_adc.convert(
|
||||||
&self.channel0.tec_u_meas_pin,
|
&self.channel0.tec_u_meas_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.tec_u_meas_adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let sample = self.pins_adc.convert(
|
let sample = self.tec_u_meas_adc.convert(
|
||||||
&self.channel1.tec_u_meas_pin,
|
&self.channel1.tec_u_meas_pin,
|
||||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||||
);
|
);
|
||||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
let mv = self.tec_u_meas_adc.sample_to_millivolts(sample);
|
||||||
ElectricPotential::new::<millivolt>(mv as f64)
|
Volts(mv as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calibrate the I_SET DAC using the DAC_FB ADC pin.
|
/// for i_set
|
||||||
///
|
|
||||||
/// These loops perform a breadth-first search for the DAC setting
|
|
||||||
/// that will produce a `target_voltage`.
|
|
||||||
pub fn calibrate_dac_value(&mut self, channel: usize) {
|
pub fn calibrate_dac_value(&mut self, channel: usize) {
|
||||||
let target_voltage = ElectricPotential::new::<volt>(2.5);
|
let vref = self.read_vref(channel);
|
||||||
let mut start_value = 1;
|
let value = self.calibrate_dac_value_for_voltage(channel, vref);
|
||||||
let mut best_error = ElectricPotential::new::<volt>(100.0);
|
info!("best dac value for {}: {}", vref, value);
|
||||||
|
|
||||||
for step in (0..18).rev() {
|
let dac_factor = value as f64 / vref.0;
|
||||||
let mut prev_value = start_value;
|
|
||||||
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
|
|
||||||
match channel {
|
|
||||||
0 => {
|
|
||||||
self.channel0.dac.set(value).unwrap();
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
self.channel1.dac.set(value).unwrap();
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let dac_feedback = self.read_dac_feedback_until_stable(channel, ElectricPotential::new::<volt>(0.001));
|
|
||||||
let error = target_voltage - dac_feedback;
|
|
||||||
if error < ElectricPotential::new::<volt>(0.0) {
|
|
||||||
break;
|
|
||||||
} else if error < best_error {
|
|
||||||
best_error = error;
|
|
||||||
start_value = prev_value;
|
|
||||||
|
|
||||||
let dac_factor = value as f64 / dac_feedback.get::<volt>();
|
|
||||||
match channel {
|
match channel {
|
||||||
0 => self.channel0.dac_factor = dac_factor,
|
0 => self.channel0.dac_factor = dac_factor,
|
||||||
1 => self.channel1.dac_factor = dac_factor,
|
1 => self.channel1.dac_factor = dac_factor,
|
||||||
@ -292,328 +218,36 @@ impl Channels {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_value = value;
|
fn calibrate_dac_value_for_voltage(&mut self, channel: usize, voltage: Volts) -> u32 {
|
||||||
}
|
let mut best_value = 0;
|
||||||
}
|
let mut best_error = Volts(100.0);
|
||||||
|
|
||||||
// Reset
|
for step in (1..=12).rev() {
|
||||||
self.set_dac(channel, ElectricPotential::new::<volt>(0.0));
|
for value in (best_value..=ad5680::MAX_VALUE).step_by(2usize.pow(step)) {
|
||||||
|
match channel {
|
||||||
|
0 => {
|
||||||
|
self.channel0.dac.set(value).unwrap();
|
||||||
|
// self.channel0.shdn.set_high().unwrap();
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
self.channel1.dac.set(value).unwrap();
|
||||||
|
// self.channel1.shdn.set_high().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// power up TEC
|
|
||||||
pub fn power_up<I: Into<usize>>(&mut self, channel: I) {
|
|
||||||
match channel.into() {
|
|
||||||
0 => self.channel0.power_up(),
|
|
||||||
1 => self.channel1.power_up(),
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// power down TEC
|
let dac_feedback = self.read_dac_feedback_until_stable(channel, 0.001);
|
||||||
pub fn power_down<I: Into<usize>>(&mut self, channel: I) {
|
let error = voltage - dac_feedback;
|
||||||
match channel.into() {
|
if error < Volts(0.0) {
|
||||||
0 => self.channel0.power_down(),
|
break;
|
||||||
1 => self.channel1.power_down(),
|
} else if error < best_error {
|
||||||
_ => unreachable!(),
|
best_value = value;
|
||||||
|
best_error = error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pwm(&self, channel: usize, pin: PwmPin) -> f64 {
|
self.set_dac(channel, Volts(0.0));
|
||||||
fn get<P: hal::PwmPin<Duty=u16>>(pin: &P) -> f64 {
|
best_value
|
||||||
let duty = pin.get_duty();
|
|
||||||
let max = pin.get_max_duty();
|
|
||||||
duty as f64 / (max as f64)
|
|
||||||
}
|
|
||||||
match (channel, pin) {
|
|
||||||
(_, PwmPin::ISet) =>
|
|
||||||
panic!("i_set is no pwm pin"),
|
|
||||||
(0, PwmPin::MaxIPos) =>
|
|
||||||
get(&self.pwm.max_i_pos0),
|
|
||||||
(0, PwmPin::MaxINeg) =>
|
|
||||||
get(&self.pwm.max_i_neg0),
|
|
||||||
(0, PwmPin::MaxV) =>
|
|
||||||
get(&self.pwm.max_v0),
|
|
||||||
(1, PwmPin::MaxIPos) =>
|
|
||||||
get(&self.pwm.max_i_pos1),
|
|
||||||
(1, PwmPin::MaxINeg) =>
|
|
||||||
get(&self.pwm.max_i_neg1),
|
|
||||||
(1, PwmPin::MaxV) =>
|
|
||||||
get(&self.pwm.max_v1),
|
|
||||||
_ =>
|
|
||||||
unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_max_v(&mut self, channel: usize) -> (ElectricPotential, ElectricPotential) {
|
|
||||||
let vref = self.channel_state(channel).vref;
|
|
||||||
let max = 4.0 * vref;
|
|
||||||
let duty = self.get_pwm(channel, PwmPin::MaxV);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let max = ElectricCurrent::new::<ampere>(3.0);
|
|
||||||
let duty = self.get_pwm(channel, PwmPin::MaxIPos);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let max = ElectricCurrent::new::<ampere>(3.0);
|
|
||||||
let duty = self.get_pwm(channel, PwmPin::MaxINeg);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
|
|
||||||
fn set<P: hal::PwmPin<Duty=u16>>(pin: &mut P, duty: f64) -> f64 {
|
|
||||||
let max = pin.get_max_duty();
|
|
||||||
let value = ((duty * (max as f64)) as u16).min(max);
|
|
||||||
pin.set_duty(value);
|
|
||||||
value as f64 / (max as f64)
|
|
||||||
}
|
|
||||||
match (channel, pin) {
|
|
||||||
(_, PwmPin::ISet) =>
|
|
||||||
panic!("i_set is no pwm pin"),
|
|
||||||
(0, PwmPin::MaxIPos) =>
|
|
||||||
set(&mut self.pwm.max_i_pos0, duty),
|
|
||||||
(0, PwmPin::MaxINeg) =>
|
|
||||||
set(&mut self.pwm.max_i_neg0, duty),
|
|
||||||
(0, PwmPin::MaxV) =>
|
|
||||||
set(&mut self.pwm.max_v0, duty),
|
|
||||||
(1, PwmPin::MaxIPos) =>
|
|
||||||
set(&mut self.pwm.max_i_pos1, duty),
|
|
||||||
(1, PwmPin::MaxINeg) =>
|
|
||||||
set(&mut self.pwm.max_i_neg1, duty),
|
|
||||||
(1, PwmPin::MaxV) =>
|
|
||||||
set(&mut self.pwm.max_v1, duty),
|
|
||||||
_ =>
|
|
||||||
unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
|
|
||||||
let vref = self.channel_state(channel).vref;
|
|
||||||
let max = 4.0 * vref;
|
|
||||||
let duty = (max_v / max).get::<ratio>();
|
|
||||||
let duty = self.set_pwm(channel, PwmPin::MaxV, duty);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let max = ElectricCurrent::new::<ampere>(3.0);
|
|
||||||
let duty = (max_i_pos / max).get::<ratio>();
|
|
||||||
let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
|
|
||||||
let max = ElectricCurrent::new::<ampere>(3.0);
|
|
||||||
let duty = (max_i_neg / max).get::<ratio>();
|
|
||||||
let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty);
|
|
||||||
(duty * max, max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report(&mut self, channel: usize) -> Report {
|
|
||||||
let vref = self.channel_state(channel).vref;
|
|
||||||
let (i_set, _) = self.get_i(channel);
|
|
||||||
let i_tec = self.read_itec(channel);
|
|
||||||
let tec_i = (i_tec - vref) / ElectricalResistance::new::<ohm>(0.4);
|
|
||||||
let (dac_value, _) = self.get_dac(channel);
|
|
||||||
let state = self.channel_state(channel);
|
|
||||||
let pid_output = state.pid.last_output.map(|last_output|
|
|
||||||
ElectricCurrent::new::<ampere>(last_output)
|
|
||||||
);
|
|
||||||
Report {
|
|
||||||
channel,
|
|
||||||
time: state.adc_time.total_millis(),
|
|
||||||
adc: state.get_adc(),
|
|
||||||
sens: state.get_sens(),
|
|
||||||
temperature: state.get_temperature()
|
|
||||||
.map(|temperature| temperature.get::<degree_celsius>()),
|
|
||||||
pid_engaged: state.pid_engaged,
|
|
||||||
i_set,
|
|
||||||
vref,
|
|
||||||
dac_value,
|
|
||||||
dac_feedback: self.read_dac_feedback(channel),
|
|
||||||
i_tec,
|
|
||||||
tec_i,
|
|
||||||
tec_u_meas: self.read_tec_u_meas(channel),
|
|
||||||
pid_output,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pwm_summary(&mut self, channel: usize) -> PwmSummary {
|
|
||||||
PwmSummary {
|
|
||||||
channel,
|
|
||||||
center: CenterPointJson(self.channel_state(channel).center.clone()),
|
|
||||||
i_set: self.get_i(channel).into(),
|
|
||||||
max_v: self.get_max_v(channel).into(),
|
|
||||||
max_i_pos: self.get_max_i_pos(channel).into(),
|
|
||||||
max_i_neg: self.get_max_i_neg(channel).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn postfilter_summary(&mut self, channel: usize) -> PostFilterSummary {
|
|
||||||
let rate = self.adc.get_postfilter(channel as u8).unwrap()
|
|
||||||
.and_then(|filter| filter.output_rate());
|
|
||||||
PostFilterSummary { channel, rate }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn steinhart_hart_summary(&mut self, channel: usize) -> SteinhartHartSummary {
|
|
||||||
let params = self.channel_state(channel).sh.clone();
|
|
||||||
SteinhartHartSummary { channel, params }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U512>;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct Report {
|
|
||||||
channel: usize,
|
|
||||||
time: i64,
|
|
||||||
adc: Option<ElectricPotential>,
|
|
||||||
sens: Option<ElectricalResistance>,
|
|
||||||
temperature: Option<f64>,
|
|
||||||
pid_engaged: bool,
|
|
||||||
i_set: ElectricCurrent,
|
|
||||||
vref: ElectricPotential,
|
|
||||||
dac_value: ElectricPotential,
|
|
||||||
dac_feedback: ElectricPotential,
|
|
||||||
i_tec: ElectricPotential,
|
|
||||||
tec_i: ElectricCurrent,
|
|
||||||
tec_u_meas: ElectricPotential,
|
|
||||||
pid_output: Option<ElectricCurrent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Report {
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CenterPointJson(CenterPoint);
|
|
||||||
|
|
||||||
// used in JSON encoding, not for config
|
|
||||||
impl Serialize for CenterPointJson {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match self.0 {
|
|
||||||
CenterPoint::Vref =>
|
|
||||||
serializer.serialize_str("vref"),
|
|
||||||
CenterPoint::Override(vref) =>
|
|
||||||
serializer.serialize_f32(vref),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct PwmSummaryField<T: Serialize> {
|
|
||||||
value: T,
|
|
||||||
max: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Serialize> From<(T, T)> for PwmSummaryField<T> {
|
|
||||||
fn from((value, max): (T, T)) -> Self {
|
|
||||||
PwmSummaryField { value, max }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct PwmSummary {
|
|
||||||
channel: usize,
|
|
||||||
center: CenterPointJson,
|
|
||||||
i_set: PwmSummaryField<ElectricCurrent>,
|
|
||||||
max_v: PwmSummaryField<ElectricPotential>,
|
|
||||||
max_i_pos: PwmSummaryField<ElectricCurrent>,
|
|
||||||
max_i_neg: PwmSummaryField<ElectricCurrent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PwmSummary {
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct PostFilterSummary {
|
|
||||||
channel: usize,
|
|
||||||
rate: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostFilterSummary {
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct SteinhartHartSummary {
|
|
||||||
channel: usize,
|
|
||||||
params: steinhart_hart::Parameters,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SteinhartHartSummary {
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn report_to_json() {
|
|
||||||
// `/ 1.1` results in values with a really long serialization
|
|
||||||
let report = Report {
|
|
||||||
channel: 0,
|
|
||||||
time: 3200,
|
|
||||||
adc: Some(ElectricPotential::new::<volt>(0.65 / 1.1)),
|
|
||||||
sens: Some(ElectricalResistance::new::<ohm>(10000.0 / 1.1)),
|
|
||||||
temperature: Some(30.0 / 1.1),
|
|
||||||
pid_engaged: false,
|
|
||||||
i_set: ElectricCurrent::new::<ampere>(0.5 / 1.1),
|
|
||||||
vref: ElectricPotential::new::<volt>(1.5 / 1.1),
|
|
||||||
dac_value: ElectricPotential::new::<volt>(2.0 / 1.1),
|
|
||||||
dac_feedback: ElectricPotential::new::<volt>(2.0 / 1.1),
|
|
||||||
i_tec: ElectricPotential::new::<volt>(2.0 / 1.1),
|
|
||||||
tec_i: ElectricCurrent::new::<ampere>(0.2 / 1.1),
|
|
||||||
tec_u_meas: ElectricPotential::new::<volt>(2.0 / 1.1),
|
|
||||||
pid_output: Some(ElectricCurrent::new::<ampere>(0.5 / 1.1)),
|
|
||||||
};
|
|
||||||
let buf = report.to_json().unwrap();
|
|
||||||
assert_eq!(buf[0], b'{');
|
|
||||||
assert_eq!(buf[buf.len() - 1], b'}');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pwm_summary_to_json() {
|
|
||||||
let value = 1.0 / 1.1;
|
|
||||||
let max = 5.0 / 1.1;
|
|
||||||
|
|
||||||
let pwm_summary = PwmSummary {
|
|
||||||
channel: 0,
|
|
||||||
center: CenterPointJson(CenterPoint::Vref),
|
|
||||||
i_set: PwmSummaryField {
|
|
||||||
value: ElectricCurrent::new::<ampere>(value),
|
|
||||||
max: ElectricCurrent::new::<ampere>(max),
|
|
||||||
},
|
|
||||||
max_v: PwmSummaryField {
|
|
||||||
value: ElectricPotential::new::<volt>(value),
|
|
||||||
max: ElectricPotential::new::<volt>(max),
|
|
||||||
},
|
|
||||||
max_i_pos: PwmSummaryField {
|
|
||||||
value: ElectricCurrent::new::<ampere>(value),
|
|
||||||
max: ElectricCurrent::new::<ampere>(max),
|
|
||||||
},
|
|
||||||
max_i_neg: PwmSummaryField {
|
|
||||||
value: ElectricCurrent::new::<ampere>(value),
|
|
||||||
max: ElectricCurrent::new::<ampere>(max),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let buf = pwm_summary.to_json().unwrap();
|
|
||||||
assert_eq!(buf[0], b'{');
|
|
||||||
assert_eq!(buf[buf.len() - 1], b'}');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ use nom::{
|
|||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
};
|
};
|
||||||
use num_traits::{Num, ParseFloatError};
|
use num_traits::{Num, ParseFloatError};
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
@ -123,35 +122,32 @@ pub enum PwmPin {
|
|||||||
MaxV,
|
MaxV,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
impl PwmPin {
|
||||||
pub enum CenterPoint {
|
pub fn name(&self) -> &'static str {
|
||||||
Vref,
|
match self {
|
||||||
Override(f32),
|
PwmPin::ISet => "i_set",
|
||||||
|
PwmPin::MaxIPos => "max_i_pos",
|
||||||
|
PwmPin::MaxINeg => "max_i_neg",
|
||||||
|
PwmPin::MaxV => "max_v",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Quit,
|
Quit,
|
||||||
Load,
|
|
||||||
Save,
|
|
||||||
Reset,
|
|
||||||
Ipv4([u8; 4]),
|
|
||||||
Show(ShowCommand),
|
Show(ShowCommand),
|
||||||
Reporting(bool),
|
Reporting(bool),
|
||||||
/// PWM parameter setting
|
/// PWM parameter setting
|
||||||
Pwm {
|
Pwm {
|
||||||
channel: usize,
|
channel: usize,
|
||||||
pin: PwmPin,
|
pin: PwmPin,
|
||||||
value: f64,
|
duty: f64,
|
||||||
},
|
},
|
||||||
/// Enable PID control for `i_set`
|
/// Enable PID control for `i_set`
|
||||||
PwmPid {
|
PwmPid {
|
||||||
channel: usize,
|
channel: usize,
|
||||||
},
|
},
|
||||||
CenterPoint {
|
|
||||||
channel: usize,
|
|
||||||
center: CenterPoint,
|
|
||||||
},
|
|
||||||
/// PID parameter setting
|
/// PID parameter setting
|
||||||
Pid {
|
Pid {
|
||||||
channel: usize,
|
channel: usize,
|
||||||
@ -165,7 +161,7 @@ pub enum Command {
|
|||||||
},
|
},
|
||||||
PostFilter {
|
PostFilter {
|
||||||
channel: usize,
|
channel: usize,
|
||||||
rate: Option<f32>,
|
rate: f32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +242,7 @@ fn report(input: &[u8]) -> IResult<&[u8], Command> {
|
|||||||
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, f64), Error>> {
|
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, f64), Error>> {
|
||||||
let result_with_pin = |pin: PwmPin|
|
let result_with_pin = |pin: PwmPin|
|
||||||
move |result: Result<f64, Error>|
|
move |result: Result<f64, Error>|
|
||||||
result.map(|value| (pin, value));
|
result.map(|duty| (pin, duty));
|
||||||
|
|
||||||
alt((
|
alt((
|
||||||
map(
|
map(
|
||||||
@ -304,8 +300,8 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
|input| {
|
|input| {
|
||||||
let (input, config) = pwm_setup(input)?;
|
let (input, config) = pwm_setup(input)?;
|
||||||
match config {
|
match config {
|
||||||
Ok((pin, value)) =>
|
Ok((pin, duty)) =>
|
||||||
Ok((input, Ok(Command::Pwm { channel, pin, value }))),
|
Ok((input, Ok(Command::Pwm { channel, pin, duty }))),
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
Ok((input, Err(e))),
|
Ok((input, Err(e))),
|
||||||
}
|
}
|
||||||
@ -318,25 +314,6 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn center_point(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|
||||||
let (input, _) = tag("center")(input)?;
|
|
||||||
let (input, _) = whitespace(input)?;
|
|
||||||
let (input, channel) = channel(input)?;
|
|
||||||
let (input, _) = whitespace(input)?;
|
|
||||||
let (input, center) = alt((
|
|
||||||
value(Ok(CenterPoint::Vref), tag("vref")),
|
|
||||||
|input| {
|
|
||||||
let (input, value) = float(input)?;
|
|
||||||
Ok((input, value.map(|value| CenterPoint::Override(value as f32))))
|
|
||||||
}
|
|
||||||
))(input)?;
|
|
||||||
end(input)?;
|
|
||||||
Ok((input, center.map(|center| Command::CenterPoint {
|
|
||||||
channel,
|
|
||||||
center,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `pid <0-1> <parameter> <value>`
|
/// `pid <0-1> <parameter> <value>`
|
||||||
fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
let (input, channel) = channel(input)?;
|
let (input, channel) = channel(input)?;
|
||||||
@ -406,56 +383,25 @@ fn postfilter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
|input| {
|
|input| {
|
||||||
let (input, channel) = channel(input)?;
|
let (input, channel) = channel(input)?;
|
||||||
let (input, _) = whitespace(input)?;
|
let (input, _) = whitespace(input)?;
|
||||||
alt((
|
|
||||||
value(Ok(Command::PostFilter {
|
|
||||||
channel,
|
|
||||||
rate: None,
|
|
||||||
}), tag("off")),
|
|
||||||
move |input| {
|
|
||||||
let (input, _) = tag("rate")(input)?;
|
let (input, _) = tag("rate")(input)?;
|
||||||
let (input, _) = whitespace(input)?;
|
let (input, _) = whitespace(input)?;
|
||||||
let (input, rate) = float(input)?;
|
let (input, rate) = float(input)?;
|
||||||
let result = rate
|
let result = rate
|
||||||
.map(|rate| Command::PostFilter {
|
.map(|rate| Command::PostFilter {
|
||||||
channel,
|
channel,
|
||||||
rate: Some(rate as f32),
|
rate: rate as f32,
|
||||||
});
|
});
|
||||||
Ok((input, result))
|
Ok((input, result))
|
||||||
}
|
}
|
||||||
))(input)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
value(Ok(Command::Show(ShowCommand::PostFilter)), end)
|
value(Ok(Command::Show(ShowCommand::PostFilter)), end)
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ipv4(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|
||||||
let (input, _) = tag("ipv4")(input)?;
|
|
||||||
let (input, _) = whitespace(input)?;
|
|
||||||
let (input, a) = unsigned(input)?;
|
|
||||||
let (input, _) = tag(".")(input)?;
|
|
||||||
let (input, b) = unsigned(input)?;
|
|
||||||
let (input, _) = tag(".")(input)?;
|
|
||||||
let (input, c) = unsigned(input)?;
|
|
||||||
let (input, _) = tag(".")(input)?;
|
|
||||||
let (input, d) = unsigned(input)?;
|
|
||||||
end(input)?;
|
|
||||||
|
|
||||||
let result = a.and_then(|a| b.and_then(|b| c.and_then(|c| d.map(|d|
|
|
||||||
Command::Ipv4([a as u8, b as u8, c as u8, d as u8])
|
|
||||||
))));
|
|
||||||
Ok((input, result))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||||
alt((value(Ok(Command::Quit), tag("quit")),
|
alt((value(Ok(Command::Quit), tag("quit")),
|
||||||
value(Ok(Command::Load), tag("load")),
|
|
||||||
value(Ok(Command::Save), tag("save")),
|
|
||||||
value(Ok(Command::Reset), tag("reset")),
|
|
||||||
ipv4,
|
|
||||||
map(report, Ok),
|
map(report, Ok),
|
||||||
pwm,
|
pwm,
|
||||||
center_point,
|
|
||||||
pid,
|
pid,
|
||||||
steinhart_hart,
|
steinhart_hart,
|
||||||
postfilter,
|
postfilter,
|
||||||
@ -465,7 +411,7 @@ fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
|||||||
impl Command {
|
impl Command {
|
||||||
pub fn parse(input: &[u8]) -> Result<Self, Error> {
|
pub fn parse(input: &[u8]) -> Result<Self, Error> {
|
||||||
match command(input) {
|
match command(input) {
|
||||||
Ok((input_remain, result)) if input_remain.len() == 0 =>
|
Ok((b"", result)) =>
|
||||||
result,
|
result,
|
||||||
Ok((input_remain, _)) =>
|
Ok((input_remain, _)) =>
|
||||||
Err(Error::UnexpectedInput(input_remain[0])),
|
Err(Error::UnexpectedInput(input_remain[0])),
|
||||||
@ -485,24 +431,6 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::Quit));
|
assert_eq!(command, Ok(Command::Quit));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_load() {
|
|
||||||
let command = Command::parse(b"load");
|
|
||||||
assert_eq!(command, Ok(Command::Load));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_save() {
|
|
||||||
let command = Command::parse(b"save");
|
|
||||||
assert_eq!(command, Ok(Command::Save));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_ipv4() {
|
|
||||||
let command = Command::parse(b"ipv4 192.168.1.26");
|
|
||||||
assert_eq!(command, Ok(Command::Ipv4([192, 168, 1, 26])));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_report() {
|
fn parse_report() {
|
||||||
let command = Command::parse(b"report");
|
let command = Command::parse(b"report");
|
||||||
@ -533,7 +461,7 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::Pwm {
|
assert_eq!(command, Ok(Command::Pwm {
|
||||||
channel: 1,
|
channel: 1,
|
||||||
pin: PwmPin::ISet,
|
pin: PwmPin::ISet,
|
||||||
value: 16383.0,
|
duty: 16383,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,6 +470,7 @@ mod test {
|
|||||||
let command = Command::parse(b"pwm 0 pid");
|
let command = Command::parse(b"pwm 0 pid");
|
||||||
assert_eq!(command, Ok(Command::PwmPid {
|
assert_eq!(command, Ok(Command::PwmPid {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
|
pin: PwmPin::ISet,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +480,7 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::Pwm {
|
assert_eq!(command, Ok(Command::Pwm {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
pin: PwmPin::MaxIPos,
|
pin: PwmPin::MaxIPos,
|
||||||
value: 7.0,
|
duty: 7,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,7 +490,7 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::Pwm {
|
assert_eq!(command, Ok(Command::Pwm {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
pin: PwmPin::MaxINeg,
|
pin: PwmPin::MaxINeg,
|
||||||
value: 128.0,
|
duty: 128,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,7 +500,7 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::Pwm {
|
assert_eq!(command, Ok(Command::Pwm {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
pin: PwmPin::MaxV,
|
pin: PwmPin::MaxV,
|
||||||
value: 32768.0,
|
duty: 32768,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,7 +537,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_steinhart_hart_set() {
|
fn parse_steinhart_hart_parallel_r() {
|
||||||
let command = Command::parse(b"s-h 1 t0 23.05");
|
let command = Command::parse(b"s-h 1 t0 23.05");
|
||||||
assert_eq!(command, Ok(Command::SteinhartHart {
|
assert_eq!(command, Ok(Command::SteinhartHart {
|
||||||
channel: 1,
|
channel: 1,
|
||||||
@ -617,45 +546,12 @@ mod test {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_postfilter() {
|
|
||||||
let command = Command::parse(b"postfilter");
|
|
||||||
assert_eq!(command, Ok(Command::Show(ShowCommand::PostFilter)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_postfilter_off() {
|
|
||||||
let command = Command::parse(b"postfilter 1 off");
|
|
||||||
assert_eq!(command, Ok(Command::PostFilter {
|
|
||||||
channel: 1,
|
|
||||||
rate: None,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_postfilter_rate() {
|
fn parse_postfilter_rate() {
|
||||||
let command = Command::parse(b"postfilter 0 rate 21");
|
let command = Command::parse(b"postfilter 0 rate 21");
|
||||||
assert_eq!(command, Ok(Command::PostFilter {
|
assert_eq!(command, Ok(Command::PostFilter {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
rate: Some(21.0),
|
rate: 21.0,
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_center_point() {
|
|
||||||
let command = Command::parse(b"center 0 1.5");
|
|
||||||
assert_eq!(command, Ok(Command::CenterPoint {
|
|
||||||
channel: 0,
|
|
||||||
center: CenterPoint::Override(1.5),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_center_point_vref() {
|
|
||||||
let command = Command::parse(b"center 1 vref");
|
|
||||||
assert_eq!(command, Ok(Command::CenterPoint {
|
|
||||||
channel: 1,
|
|
||||||
center: CenterPoint::Vref,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
253
src/config.rs
253
src/config.rs
@ -1,253 +0,0 @@
|
|||||||
use postcard::{from_bytes, to_slice};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use smoltcp::wire::Ipv4Address;
|
|
||||||
use stm32f4xx_hal::i2c;
|
|
||||||
use uom::si::{
|
|
||||||
electric_potential::volt,
|
|
||||||
electric_current::ampere,
|
|
||||||
electrical_resistance::ohm,
|
|
||||||
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance, ThermodynamicTemperature},
|
|
||||||
thermodynamic_temperature::degree_celsius,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
ad7172::PostFilter,
|
|
||||||
channels::{CHANNELS, Channels},
|
|
||||||
command_parser::CenterPoint,
|
|
||||||
EEPROM_SIZE, EEPROM_PAGE_SIZE,
|
|
||||||
pid,
|
|
||||||
pins,
|
|
||||||
steinhart_hart,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Eeprom(eeprom24x::Error<i2c::Error>),
|
|
||||||
Encode(postcard::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<eeprom24x::Error<i2c::Error>> for Error {
|
|
||||||
fn from(e: eeprom24x::Error<i2c::Error>) -> Self {
|
|
||||||
Error::Eeprom(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<postcard::Error> for Error {
|
|
||||||
fn from(e: postcard::Error) -> Self {
|
|
||||||
Error::Encode(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Just for encoding/decoding, actual state resides in ChannelState
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
channels: [ChannelConfig; CHANNELS],
|
|
||||||
pub ipv4_address: [u8; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new(channels: &mut Channels, ipv4_address: Ipv4Address) -> Self {
|
|
||||||
Config {
|
|
||||||
channels: [
|
|
||||||
ChannelConfig::new(channels, 0),
|
|
||||||
ChannelConfig::new(channels, 1),
|
|
||||||
],
|
|
||||||
ipv4_address: ipv4_address.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// apply loaded config to system
|
|
||||||
pub fn apply(&self, channels: &mut Channels) {
|
|
||||||
for i in 0..CHANNELS {
|
|
||||||
self.channels[i].apply(channels, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(eeprom: &mut pins::Eeprom) -> Result<Self, Error> {
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
eeprom.read_data(0, &mut buffer)?;
|
|
||||||
log::info!("load: {:?}", buffer);
|
|
||||||
let config = from_bytes(&mut buffer)?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&self, eeprom: &mut pins::Eeprom) -> Result<(), Error> {
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
let config_buffer = to_slice(self, &mut buffer)?;
|
|
||||||
log::info!("save: {:?}", config_buffer);
|
|
||||||
|
|
||||||
let mut addr = 0;
|
|
||||||
for chunk in config_buffer.chunks(EEPROM_PAGE_SIZE) {
|
|
||||||
'write_retry: loop {
|
|
||||||
match eeprom.write_page(addr, chunk) {
|
|
||||||
Ok(()) => break 'write_retry,
|
|
||||||
Err(eeprom24x::Error::I2C(i2c::Error::NACK)) => {},
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr += chunk.len() as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct ChannelConfig {
|
|
||||||
center: CenterPoint,
|
|
||||||
pid: pid::Parameters,
|
|
||||||
pid_target: f32,
|
|
||||||
sh: SteinhartHartConfig,
|
|
||||||
pwm: PwmLimits,
|
|
||||||
/// uses variant `PostFilter::Invalid` instead of `None` to save space
|
|
||||||
adc_postfilter: PostFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelConfig {
|
|
||||||
pub fn new(channels: &mut Channels, channel: usize) -> Self {
|
|
||||||
let pwm = PwmLimits::new(channels, channel);
|
|
||||||
|
|
||||||
let adc_postfilter = channels.adc.get_postfilter(channel as u8)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap_or(PostFilter::Invalid);
|
|
||||||
|
|
||||||
let state = channels.channel_state(channel);
|
|
||||||
ChannelConfig {
|
|
||||||
center: state.center.clone(),
|
|
||||||
pid: state.pid.parameters.clone(),
|
|
||||||
pid_target: state.pid.target as f32,
|
|
||||||
sh: (&state.sh).into(),
|
|
||||||
pwm,
|
|
||||||
adc_postfilter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, channels: &mut Channels, channel: usize) {
|
|
||||||
let state = channels.channel_state(channel);
|
|
||||||
state.center = self.center.clone();
|
|
||||||
state.pid.parameters = self.pid.clone();
|
|
||||||
state.pid.target = self.pid_target.into();
|
|
||||||
state.sh = (&self.sh).into();
|
|
||||||
|
|
||||||
self.pwm.apply(channels, channel);
|
|
||||||
|
|
||||||
let adc_postfilter = match self.adc_postfilter {
|
|
||||||
PostFilter::Invalid => None,
|
|
||||||
adc_postfilter => Some(adc_postfilter),
|
|
||||||
};
|
|
||||||
let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
struct SteinhartHartConfig {
|
|
||||||
t0: f32,
|
|
||||||
r0: f32,
|
|
||||||
b: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&steinhart_hart::Parameters> for SteinhartHartConfig {
|
|
||||||
fn from(sh: &steinhart_hart::Parameters) -> Self {
|
|
||||||
SteinhartHartConfig {
|
|
||||||
t0: sh.t0.get::<degree_celsius>() as f32,
|
|
||||||
r0: sh.r0.get::<ohm>() as f32,
|
|
||||||
b: sh.b as f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<steinhart_hart::Parameters> for &SteinhartHartConfig {
|
|
||||||
fn into(self) -> steinhart_hart::Parameters {
|
|
||||||
steinhart_hart::Parameters {
|
|
||||||
t0: ThermodynamicTemperature::new::<degree_celsius>(self.t0.into()),
|
|
||||||
r0: ElectricalResistance::new::<ohm>(self.r0.into()),
|
|
||||||
b: self.b.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
struct PwmLimits {
|
|
||||||
max_v: f32,
|
|
||||||
max_i_pos: f32,
|
|
||||||
max_i_neg: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PwmLimits {
|
|
||||||
pub fn new(channels: &mut Channels, channel: usize) -> Self {
|
|
||||||
let (max_v, _) = channels.get_max_v(channel);
|
|
||||||
let (max_i_pos, _) = channels.get_max_i_pos(channel);
|
|
||||||
let (max_i_neg, _) = channels.get_max_i_neg(channel);
|
|
||||||
PwmLimits {
|
|
||||||
max_v: max_v.get::<volt>() as f32,
|
|
||||||
max_i_pos: max_i_pos.get::<ampere>() as f32,
|
|
||||||
max_i_neg: max_i_neg.get::<ampere>() as f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, channels: &mut Channels, channel: usize) {
|
|
||||||
channels.set_max_v(channel, ElectricPotential::new::<volt>(self.max_v.into()));
|
|
||||||
channels.set_max_i_pos(channel, ElectricCurrent::new::<ampere>(self.max_i_pos.into()));
|
|
||||||
channels.set_max_i_neg(channel, ElectricCurrent::new::<ampere>(self.max_i_neg.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::DEFAULT_IPV4_ADDRESS;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fit_eeprom() {
|
|
||||||
let channel_config = ChannelConfig {
|
|
||||||
center: CenterPoint::Override(1.5),
|
|
||||||
pid: pid::Parameters::default(),
|
|
||||||
pid_target: 93.7,
|
|
||||||
sh: (&steinhart_hart::Parameters::default()).into(),
|
|
||||||
pwm: PwmLimits {
|
|
||||||
max_v: 1.65,
|
|
||||||
max_i_pos: 2.1,
|
|
||||||
max_i_neg: 2.25,
|
|
||||||
},
|
|
||||||
adc_postfilter: PostFilter::F21SPS,
|
|
||||||
};
|
|
||||||
let config = Config {
|
|
||||||
channels: [
|
|
||||||
channel_config.clone(),
|
|
||||||
channel_config.clone(),
|
|
||||||
],
|
|
||||||
ipv4_address: DEFAULT_IPV4_ADDRESS.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
let buffer = to_slice(&config, &mut buffer).unwrap();
|
|
||||||
assert!(buffer.len() <= EEPROM_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encode_decode() {
|
|
||||||
let channel_config = ChannelConfig {
|
|
||||||
center: CenterPoint::Override(1.5),
|
|
||||||
pid: pid::Parameters::default(),
|
|
||||||
pid_target: 93.7,
|
|
||||||
sh: (&steinhart_hart::Parameters::default()).into(),
|
|
||||||
pwm: PwmLimits {
|
|
||||||
max_v: 1.65,
|
|
||||||
max_i_pos: 2.1,
|
|
||||||
max_i_neg: 2.25,
|
|
||||||
},
|
|
||||||
adc_postfilter: PostFilter::F21SPS,
|
|
||||||
};
|
|
||||||
let config = Config {
|
|
||||||
channels: [
|
|
||||||
channel_config.clone(),
|
|
||||||
channel_config.clone(),
|
|
||||||
],
|
|
||||||
ipv4_address: DEFAULT_IPV4_ADDRESS.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buffer = [0; EEPROM_SIZE];
|
|
||||||
to_slice(&config, &mut buffer).unwrap();
|
|
||||||
let decoded: Config = from_bytes(&buffer).unwrap();
|
|
||||||
assert_eq!(decoded, config);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,5 @@
|
|||||||
use crate::usb;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "semihosting"))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
pub fn init_log() {
|
pub fn init_log() {}
|
||||||
static USB_LOGGER: usb::Logger = usb::Logger;
|
|
||||||
let _ = log::set_logger(&USB_LOGGER);
|
|
||||||
log::set_max_level(log::LevelFilter::Debug);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "semihosting")]
|
#[cfg(feature = "semihosting")]
|
||||||
pub fn init_log() {
|
pub fn init_log() {
|
||||||
|
44
src/leds.rs
44
src/leds.rs
@ -1,44 +0,0 @@
|
|||||||
use stm32f4xx_hal::{
|
|
||||||
gpio::{
|
|
||||||
gpiod::{PD9, PD10, PD11},
|
|
||||||
Output, PushPull,
|
|
||||||
},
|
|
||||||
hal::digital::v2::OutputPin,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Leds {
|
|
||||||
/// Red LED L1
|
|
||||||
pub r1: Led<PD9<Output<PushPull>>>,
|
|
||||||
/// Green LED L3
|
|
||||||
pub g3: Led<PD10<Output<PushPull>>>,
|
|
||||||
/// Green LED L4
|
|
||||||
pub g4: Led<PD11<Output<PushPull>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Leds {
|
|
||||||
pub fn new<M1, M2, M3>(r1: PD9<M1>, g3: PD10<M2>, g4: PD11<M3>) -> Self {
|
|
||||||
Leds {
|
|
||||||
r1: Led::new(r1.into_push_pull_output()),
|
|
||||||
g3: Led::new(g3.into_push_pull_output()),
|
|
||||||
g4: Led::new(g4.into_push_pull_output()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Led<P> {
|
|
||||||
pin: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: OutputPin> Led<P> {
|
|
||||||
pub fn new(pin: P) -> Self {
|
|
||||||
Led { pin }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on(&mut self) {
|
|
||||||
let _ = self.pin.set_high();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn off(&mut self) {
|
|
||||||
let _ = self.pin.set_low();
|
|
||||||
}
|
|
||||||
}
|
|
429
src/main.rs
429
src/main.rs
@ -1,133 +1,82 @@
|
|||||||
#![cfg_attr(not(test), no_std)]
|
#![no_std]
|
||||||
#![cfg_attr(not(test), no_main)]
|
#![no_main]
|
||||||
#![feature(maybe_uninit_extra, maybe_uninit_ref)]
|
|
||||||
#![cfg_attr(test, allow(unused))]
|
|
||||||
// TODO: #![deny(warnings, unused)]
|
// TODO: #![deny(warnings, unused)]
|
||||||
|
|
||||||
#[cfg(not(any(feature = "semihosting", test)))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
use panic_abort as _;
|
use panic_abort as _;
|
||||||
#[cfg(all(feature = "semihosting", not(test)))]
|
#[cfg(feature = "semihosting")]
|
||||||
use panic_semihosting as _;
|
use panic_semihosting as _;
|
||||||
|
|
||||||
use log::{error, info, warn};
|
use log::{info, warn};
|
||||||
|
|
||||||
|
use core::ops::DerefMut;
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use cortex_m::asm::wfi;
|
use cortex_m::asm::wfi;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use stm32f4xx_hal::{
|
use stm32f4xx_hal::{
|
||||||
hal::watchdog::{WatchdogEnable, Watchdog},
|
hal::{
|
||||||
|
self,
|
||||||
|
watchdog::{WatchdogEnable, Watchdog},
|
||||||
|
},
|
||||||
rcc::RccExt,
|
rcc::RccExt,
|
||||||
watchdog::IndependentWatchdog,
|
watchdog::IndependentWatchdog,
|
||||||
time::{U32Ext, MegaHertz},
|
time::{U32Ext, MegaHertz},
|
||||||
stm32::{CorePeripherals, Peripherals, SCB},
|
stm32::{CorePeripherals, Peripherals},
|
||||||
};
|
};
|
||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
socket::TcpSocket,
|
wire::EthernetAddress,
|
||||||
wire::{EthernetAddress, Ipv4Address},
|
|
||||||
};
|
|
||||||
use uom::{
|
|
||||||
si::{
|
|
||||||
f64::{
|
|
||||||
ElectricCurrent,
|
|
||||||
ElectricPotential,
|
|
||||||
ElectricalResistance,
|
|
||||||
ThermodynamicTemperature,
|
|
||||||
},
|
|
||||||
electric_current::ampere,
|
|
||||||
electric_potential::volt,
|
|
||||||
electrical_resistance::ohm,
|
|
||||||
thermodynamic_temperature::degree_celsius,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod init_log;
|
mod init_log;
|
||||||
use init_log::init_log;
|
use init_log::init_log;
|
||||||
mod usb;
|
|
||||||
mod leds;
|
|
||||||
mod pins;
|
mod pins;
|
||||||
use pins::Pins;
|
use pins::Pins;
|
||||||
|
mod softspi;
|
||||||
mod ad7172;
|
mod ad7172;
|
||||||
mod ad5680;
|
mod ad5680;
|
||||||
mod net;
|
mod net;
|
||||||
mod server;
|
mod server;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
mod session;
|
mod session;
|
||||||
use session::{Session, SessionInput};
|
use session::{Session, SessionOutput};
|
||||||
mod command_parser;
|
mod command_parser;
|
||||||
use command_parser::{Command, ShowCommand, PwmPin};
|
use command_parser::{Command, ShowCommand, PwmPin};
|
||||||
mod timer;
|
mod timer;
|
||||||
|
mod units;
|
||||||
|
use units::{Ohms, Volts};
|
||||||
mod pid;
|
mod pid;
|
||||||
mod steinhart_hart;
|
mod steinhart_hart;
|
||||||
mod channels;
|
mod channels;
|
||||||
use channels::{CHANNELS, Channels};
|
use channels::{CHANNELS, Channels};
|
||||||
mod channel;
|
mod channel;
|
||||||
mod channel_state;
|
mod channel_state;
|
||||||
mod config;
|
|
||||||
use config::Config;
|
|
||||||
|
|
||||||
|
|
||||||
const HSE: MegaHertz = MegaHertz(8);
|
const HSE: MegaHertz = MegaHertz(8);
|
||||||
#[cfg(not(feature = "semihosting"))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
const WATCHDOG_INTERVAL: u32 = 1_000;
|
const WATCHDOG_INTERVAL: u32 = 100;
|
||||||
#[cfg(feature = "semihosting")]
|
#[cfg(feature = "semihosting")]
|
||||||
const WATCHDOG_INTERVAL: u32 = 30_000;
|
const WATCHDOG_INTERVAL: u32 = 30_000;
|
||||||
|
|
||||||
pub const EEPROM_PAGE_SIZE: usize = 8;
|
#[cfg(not(feature = "generate-hwaddr"))]
|
||||||
pub const EEPROM_SIZE: usize = 128;
|
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
|
||||||
|
|
||||||
pub const DEFAULT_IPV4_ADDRESS: Ipv4Address = Ipv4Address([192, 168, 1, 26]);
|
|
||||||
const TCP_PORT: u16 = 23;
|
const TCP_PORT: u16 = 23;
|
||||||
|
|
||||||
|
|
||||||
fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool {
|
|
||||||
let send_free = socket.send_capacity() - socket.send_queue();
|
|
||||||
if data.len() > send_free + 1 {
|
|
||||||
// Not enough buffer space, skip report for now
|
|
||||||
warn!(
|
|
||||||
"TCP socket has only {}/{} needed {}",
|
|
||||||
send_free + 1, socket.send_capacity(), data.len(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
match socket.send_slice(&data) {
|
|
||||||
Ok(sent) if sent == data.len() => {
|
|
||||||
let _ = socket.send_slice(b"\n");
|
|
||||||
// success
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
Ok(sent) =>
|
|
||||||
warn!("sent only {}/{} bytes", sent, data.len()),
|
|
||||||
Err(e) =>
|
|
||||||
error!("error sending line: {:?}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// not success
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_to(channel: usize, channels: &mut Channels, socket: &mut TcpSocket) -> bool {
|
|
||||||
match channels.report(channel).to_json() {
|
|
||||||
Ok(buf) =>
|
|
||||||
send_line(socket, &buf[..]),
|
|
||||||
Err(e) => {
|
|
||||||
error!("unable to serialize report: {:?}", e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialization and main loop
|
/// Initialization and main loop
|
||||||
#[cfg(not(test))]
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
init_log();
|
init_log();
|
||||||
info!("thermostat");
|
info!("tecpak");
|
||||||
|
|
||||||
let mut cp = CorePeripherals::take().unwrap();
|
let mut cp = CorePeripherals::take().unwrap();
|
||||||
cp.SCB.enable_icache();
|
cp.SCB.enable_icache();
|
||||||
cp.SCB.enable_dcache(&mut cp.CPUID);
|
cp.SCB.enable_dcache(&mut cp.CPUID);
|
||||||
|
|
||||||
let dp = Peripherals::take().unwrap();
|
let dp = Peripherals::take().unwrap();
|
||||||
|
stm32_eth::setup(&dp.RCC, &dp.SYSCFG);
|
||||||
|
|
||||||
let clocks = dp.RCC.constrain()
|
let clocks = dp.RCC.constrain()
|
||||||
.cfgr
|
.cfgr
|
||||||
.use_hse(HSE)
|
.use_hse(HSE)
|
||||||
@ -143,44 +92,26 @@ fn main() -> ! {
|
|||||||
|
|
||||||
timer::setup(cp.SYST, clocks);
|
timer::setup(cp.SYST, clocks);
|
||||||
|
|
||||||
let (pins, mut leds, mut eeprom, eth_pins, usb) = Pins::setup(
|
let pins = Pins::setup(
|
||||||
clocks, dp.TIM1, dp.TIM3,
|
clocks, dp.TIM1, dp.TIM3,
|
||||||
dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOD, dp.GPIOE, dp.GPIOF, dp.GPIOG,
|
dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOE, dp.GPIOF, dp.GPIOG,
|
||||||
dp.I2C1,
|
|
||||||
dp.SPI2, dp.SPI4, dp.SPI5,
|
dp.SPI2, dp.SPI4, dp.SPI5,
|
||||||
dp.ADC1,
|
dp.ADC1, dp.ADC2, dp.ADC3,
|
||||||
dp.OTG_FS_GLOBAL,
|
|
||||||
dp.OTG_FS_DEVICE,
|
|
||||||
dp.OTG_FS_PWRCLK,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
leds.r1.on();
|
|
||||||
leds.g3.off();
|
|
||||||
leds.g4.off();
|
|
||||||
|
|
||||||
usb::State::setup(usb);
|
|
||||||
|
|
||||||
let mut ipv4_address = DEFAULT_IPV4_ADDRESS;
|
|
||||||
let mut channels = Channels::new(pins);
|
let mut channels = Channels::new(pins);
|
||||||
let _ = Config::load(&mut eeprom)
|
channels.calibrate_dac_value(0);
|
||||||
.map(|config| {
|
|
||||||
config.apply(&mut channels);
|
|
||||||
ipv4_address = Ipv4Address::from_bytes(&config.ipv4_address);
|
|
||||||
})
|
|
||||||
.map_err(|e| warn!("error loading config: {:?}", e));
|
|
||||||
info!("IPv4 address: {}", ipv4_address);
|
|
||||||
|
|
||||||
// EEPROM ships with a read-only EUI-48 identifier
|
#[cfg(not(feature = "generate-hwaddr"))]
|
||||||
let mut eui48 = [0; 6];
|
let hwaddr = EthernetAddress(NET_HWADDR);
|
||||||
eeprom.read_data(0xFA, &mut eui48).unwrap();
|
#[cfg(feature = "generate-hwaddr")]
|
||||||
let hwaddr = EthernetAddress(eui48);
|
let hwaddr = {
|
||||||
info!("EEPROM MAC address: {}", hwaddr);
|
let uid = stm32f4xx_hal::signature::Uid::get();
|
||||||
|
EthernetAddress(hash2hwaddr::generate_hwaddr(uid))
|
||||||
|
};
|
||||||
|
info!("Net hwaddr: {}", hwaddr);
|
||||||
|
|
||||||
net::run(clocks, dp.ETHERNET_MAC, dp.ETHERNET_DMA, eth_pins, hwaddr, ipv4_address, |iface| {
|
net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
|
||||||
let mut new_ipv4_address = None;
|
|
||||||
Server::<Session>::run(iface, |server| {
|
Server::<Session>::run(iface, |server| {
|
||||||
leds.r1.off();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let instant = Instant::from_millis(i64::from(timer::now()));
|
let instant = Instant::from_millis(i64::from(timer::now()));
|
||||||
let updated_channel = channels.poll_adc(instant);
|
let updated_channel = channels.poll_adc(instant);
|
||||||
@ -200,103 +131,178 @@ fn main() -> ! {
|
|||||||
if ! socket.is_active() {
|
if ! socket.is_active() {
|
||||||
let _ = socket.listen(TCP_PORT);
|
let _ = socket.listen(TCP_PORT);
|
||||||
session.reset();
|
session.reset();
|
||||||
} else if socket.may_send() && !socket.may_recv() {
|
} else if socket.can_send() && socket.can_recv() && socket.send_capacity() - socket.send_queue() > 1024 {
|
||||||
socket.close()
|
|
||||||
} else if socket.can_send() && socket.can_recv() {
|
|
||||||
match socket.recv(|buf| session.feed(buf)) {
|
match socket.recv(|buf| session.feed(buf)) {
|
||||||
Ok(SessionInput::Nothing) => {}
|
Ok(SessionOutput::Nothing) => {}
|
||||||
Ok(SessionInput::Command(command)) => match command {
|
Ok(SessionOutput::Command(command)) => match command {
|
||||||
Command::Quit =>
|
Command::Quit =>
|
||||||
socket.close(),
|
socket.close(),
|
||||||
Command::Reporting(_reporting) => {
|
Command::Reporting(reporting) => {
|
||||||
// handled by session
|
let _ = writeln!(socket, "report={}", if reporting { "on" } else { "off" });
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::Reporting) => {
|
Command::Show(ShowCommand::Reporting) => {
|
||||||
let _ = writeln!(socket, "{{ \"report\": {:?} }}", session.reporting());
|
let _ = writeln!(socket, "report={}", if session.reporting() { "on" } else { "off" });
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::Input) => {
|
Command::Show(ShowCommand::Input) => {
|
||||||
for channel in 0..CHANNELS {
|
for channel in 0..CHANNELS {
|
||||||
report_to(channel, &mut channels, &mut socket);
|
if let Some(adc_data) = channels.channel_state(channel).adc_data {
|
||||||
|
let vref = channels.read_vref(channel);
|
||||||
|
let dac_feedback = channels.read_dac_feedback(channel);
|
||||||
|
|
||||||
|
let itec = channels.read_itec(channel);
|
||||||
|
let tec_i = -(itec - Volts(1.5)) / Ohms(0.4);
|
||||||
|
|
||||||
|
let tec_u_meas = channels.read_tec_u_meas(channel);
|
||||||
|
|
||||||
|
let state = channels.channel_state(channel);
|
||||||
|
let _ = writeln!(
|
||||||
|
socket, "t={} adc_raw{}=0x{:06X} vref={} dac_feedback={} itec={} tec={} tec_u_meas={}",
|
||||||
|
state.adc_time, channel, adc_data,
|
||||||
|
vref, dac_feedback,
|
||||||
|
itec, tec_i,
|
||||||
|
tec_u_meas,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::Pid) => {
|
Command::Show(ShowCommand::Pid) => {
|
||||||
for channel in 0..CHANNELS {
|
for channel in 0..CHANNELS {
|
||||||
match channels.channel_state(channel).pid.summary(channel).to_json() {
|
let state = channels.channel_state(channel);
|
||||||
Ok(buf) => {
|
let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||||
send_line(&mut socket, &buf);
|
let pid = &state.pid;
|
||||||
|
let _ = writeln!(socket, "- target={:.4}", pid.target);
|
||||||
|
macro_rules! show_pid_parameter {
|
||||||
|
($p: tt) => {
|
||||||
|
let _ = writeln!(
|
||||||
|
socket, "- {}={:.4}",
|
||||||
|
stringify!($p), pid.parameters.$p
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Err(e) =>
|
show_pid_parameter!(kp);
|
||||||
error!("unable to serialize pid summary: {:?}", e),
|
show_pid_parameter!(ki);
|
||||||
|
show_pid_parameter!(kd);
|
||||||
|
show_pid_parameter!(integral_min);
|
||||||
|
show_pid_parameter!(integral_max);
|
||||||
|
show_pid_parameter!(output_min);
|
||||||
|
show_pid_parameter!(output_max);
|
||||||
|
if let Some(last_output) = pid.last_output {
|
||||||
|
let _ = writeln!(socket, "- last_output={:.4}", last_output);
|
||||||
}
|
}
|
||||||
|
let _ = writeln!(socket, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::Pwm) => {
|
Command::Show(ShowCommand::Pwm) => {
|
||||||
for channel in 0..CHANNELS {
|
for channel in 0..CHANNELS {
|
||||||
match channels.pwm_summary(channel).to_json() {
|
let state = channels.channel_state(channel);
|
||||||
Ok(buf) => {
|
let _ = writeln!(
|
||||||
send_line(&mut socket, &buf);
|
socket, "channel {}: PID={}",
|
||||||
|
channel,
|
||||||
|
if state.pid_engaged { "engaged" } else { "disengaged" }
|
||||||
|
);
|
||||||
|
let _ = writeln!(socket, "- i_set={}", state.dac_value);
|
||||||
|
fn show_pwm_channel<S, P>(mut socket: S, name: &str, pin: &P)
|
||||||
|
where
|
||||||
|
S: core::fmt::Write,
|
||||||
|
P: hal::PwmPin<Duty=u16>,
|
||||||
|
{
|
||||||
|
let _ = writeln!(
|
||||||
|
socket,
|
||||||
|
"- {}={}/{}",
|
||||||
|
name, pin.get_duty(), pin.get_max_duty()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) =>
|
match channel {
|
||||||
error!("unable to serialize pwm summary: {:?}", e),
|
0 => {
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_v", &channels.pwm.max_v0);
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_i_pos", &channels.pwm.max_i_pos0);
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_i_neg", &channels.pwm.max_i_neg0);
|
||||||
}
|
}
|
||||||
|
1 => {
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_v", &channels.pwm.max_v1);
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_i_pos", &channels.pwm.max_i_pos1);
|
||||||
|
show_pwm_channel(socket.deref_mut(), "max_i_neg", &channels.pwm.max_i_neg1);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
let _ = writeln!(socket, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::SteinhartHart) => {
|
Command::Show(ShowCommand::SteinhartHart) => {
|
||||||
for channel in 0..CHANNELS {
|
for channel in 0..CHANNELS {
|
||||||
match channels.steinhart_hart_summary(channel).to_json() {
|
let state = channels.channel_state(channel);
|
||||||
Ok(buf) => {
|
let _ = writeln!(
|
||||||
send_line(&mut socket, &buf);
|
socket, "channel {}: Steinhart-Hart equation parameters",
|
||||||
}
|
channel,
|
||||||
Err(e) =>
|
);
|
||||||
error!("unable to serialize steinhart-hart summary: {:?}", e),
|
let _ = writeln!(socket, "- t0={}", state.sh.t0);
|
||||||
}
|
let _ = writeln!(socket, "- b={}", state.sh.b);
|
||||||
|
let _ = writeln!(socket, "- r0={}", state.sh.r0);
|
||||||
|
let _ = writeln!(socket, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Show(ShowCommand::PostFilter) => {
|
Command::Show(ShowCommand::PostFilter) => {
|
||||||
for channel in 0..CHANNELS {
|
for channel in 0..CHANNELS {
|
||||||
match channels.postfilter_summary(channel).to_json() {
|
match channels.adc.get_postfilter(channel as u8).unwrap() {
|
||||||
Ok(buf) => {
|
Some(filter) => {
|
||||||
send_line(&mut socket, &buf);
|
let _ = writeln!(
|
||||||
|
socket, "channel {}: postfilter={:.2} SPS",
|
||||||
|
channel, filter.output_rate().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let _ = writeln!(
|
||||||
|
socket, "channel {}: no postfilter",
|
||||||
|
channel
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) =>
|
|
||||||
error!("unable to serialize postfilter summary: {:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::PwmPid { channel } => {
|
Command::PwmPid { channel } => {
|
||||||
channels.channel_state(channel).pid_engaged = true;
|
channels.channel_state(channel).pid_engaged = true;
|
||||||
leds.g3.on();
|
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Command::Pwm { channel, pin, value } => {
|
Command::Pwm { channel, pin: PwmPin::ISet, duty } => {
|
||||||
match pin {
|
|
||||||
PwmPin::ISet => {
|
|
||||||
channels.channel_state(channel).pid_engaged = false;
|
channels.channel_state(channel).pid_engaged = false;
|
||||||
leds.g3.off();
|
let voltage = Volts(duty);
|
||||||
let current = ElectricCurrent::new::<ampere>(value);
|
channels.set_dac(channel, voltage);
|
||||||
channels.set_i(channel, current);
|
let _ = writeln!(
|
||||||
channels.power_up(channel);
|
socket, "channel {}: PWM duty cycle manually set to {}",
|
||||||
|
channel, voltage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PwmPin::MaxV => {
|
Command::Pwm { channel, pin, duty } => {
|
||||||
let voltage = ElectricPotential::new::<volt>(value);
|
fn set_pwm_channel<P: hal::PwmPin<Duty=u16>>(pin: &mut P, duty: f64) -> (u16, u16) {
|
||||||
channels.set_max_v(channel, voltage);
|
let max = pin.get_max_duty();
|
||||||
}
|
let value = (duty * (max as f64)) as u16;
|
||||||
PwmPin::MaxIPos => {
|
pin.set_duty(value);
|
||||||
let current = ElectricCurrent::new::<ampere>(value);
|
(value, max)
|
||||||
channels.set_max_i_pos(channel, current);
|
|
||||||
}
|
|
||||||
PwmPin::MaxINeg => {
|
|
||||||
let current = ElectricCurrent::new::<ampere>(value);
|
|
||||||
channels.set_max_i_neg(channel, current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::CenterPoint { channel, center } => {
|
|
||||||
let (i_tec, _) = channels.get_i(channel);
|
|
||||||
let state = channels.channel_state(channel);
|
|
||||||
state.center = center;
|
|
||||||
if !state.pid_engaged {
|
|
||||||
channels.set_i(channel, i_tec);
|
|
||||||
}
|
}
|
||||||
|
let (value, max) = match (channel, pin) {
|
||||||
|
(_, PwmPin::ISet) =>
|
||||||
|
// Handled above
|
||||||
|
unreachable!(),
|
||||||
|
(0, PwmPin::MaxIPos) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_i_pos0, duty),
|
||||||
|
(0, PwmPin::MaxINeg) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_i_neg0, duty),
|
||||||
|
(0, PwmPin::MaxV) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_v0, duty),
|
||||||
|
(1, PwmPin::MaxIPos) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_i_pos1, duty),
|
||||||
|
(1, PwmPin::MaxINeg) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_i_neg1, duty),
|
||||||
|
(1, PwmPin::MaxV) =>
|
||||||
|
set_pwm_channel(&mut channels.pwm.max_v1, duty),
|
||||||
|
_ =>
|
||||||
|
unreachable!(),
|
||||||
|
};
|
||||||
|
let _ = writeln!(
|
||||||
|
socket, "channel {}: PWM {} reconfigured to {}/{}",
|
||||||
|
channel, pin.name(), value, max
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Command::Pid { channel, parameter, value } => {
|
Command::Pid { channel, parameter, value } => {
|
||||||
let pid = &mut channels.channel_state(channel).pid;
|
let pid = &mut channels.channel_state(channel).pid;
|
||||||
@ -305,105 +311,80 @@ fn main() -> ! {
|
|||||||
Target =>
|
Target =>
|
||||||
pid.target = value,
|
pid.target = value,
|
||||||
KP =>
|
KP =>
|
||||||
pid.parameters.kp = value as f32,
|
pid.parameters.kp = value,
|
||||||
KI =>
|
KI =>
|
||||||
pid.parameters.ki = value as f32,
|
pid.parameters.ki = value,
|
||||||
KD =>
|
KD =>
|
||||||
pid.parameters.kd = value as f32,
|
pid.parameters.kd = value,
|
||||||
OutputMin =>
|
OutputMin =>
|
||||||
pid.parameters.output_min = value as f32,
|
pid.parameters.output_min = value,
|
||||||
OutputMax =>
|
OutputMax =>
|
||||||
pid.parameters.output_max = value as f32,
|
pid.parameters.output_max = value,
|
||||||
IntegralMin =>
|
IntegralMin =>
|
||||||
pid.parameters.integral_min = value as f32,
|
pid.parameters.integral_min = value,
|
||||||
IntegralMax =>
|
IntegralMax =>
|
||||||
pid.parameters.integral_max = value as f32,
|
pid.parameters.integral_max = value,
|
||||||
}
|
}
|
||||||
|
// TODO: really reset PID state
|
||||||
|
// after each parameter change?
|
||||||
|
pid.reset();
|
||||||
|
let _ = writeln!(socket, "PID parameter updated");
|
||||||
}
|
}
|
||||||
Command::SteinhartHart { channel, parameter, value } => {
|
Command::SteinhartHart { channel, parameter, value } => {
|
||||||
let sh = &mut channels.channel_state(channel).sh;
|
let sh = &mut channels.channel_state(channel).sh;
|
||||||
use command_parser::ShParameter::*;
|
use command_parser::ShParameter::*;
|
||||||
match parameter {
|
match parameter {
|
||||||
T0 => sh.t0 = ThermodynamicTemperature::new::<degree_celsius>(value),
|
T0 => sh.t0 = value,
|
||||||
B => sh.b = value,
|
B => sh.b = value,
|
||||||
R0 => sh.r0 = ElectricalResistance::new::<ohm>(value),
|
R0 => sh.r0 = value,
|
||||||
}
|
}
|
||||||
|
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
|
||||||
}
|
}
|
||||||
Command::PostFilter { channel, rate: None } => {
|
Command::PostFilter { channel, rate } => {
|
||||||
channels.adc.set_postfilter(channel as u8, None).unwrap();
|
|
||||||
}
|
|
||||||
Command::PostFilter { channel, rate: Some(rate) } => {
|
|
||||||
let filter = ad7172::PostFilter::closest(rate);
|
let filter = ad7172::PostFilter::closest(rate);
|
||||||
match filter {
|
match filter {
|
||||||
Some(filter) =>
|
Some(filter) => {
|
||||||
channels.adc.set_postfilter(channel as u8, Some(filter)).unwrap(),
|
channels.adc.set_postfilter(channel as u8, Some(filter)).unwrap();
|
||||||
None =>
|
let _ = writeln!(
|
||||||
error!("unable to choose postfilter for rate {:.3}", rate),
|
socket, "channel {}: postfilter set to {:.2} SPS",
|
||||||
|
channel, filter.output_rate().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let _ = writeln!(socket, "Unable to choose postfilter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Load => {
|
|
||||||
match Config::load(&mut eeprom) {
|
|
||||||
Ok(config) => {
|
|
||||||
config.apply(&mut channels);
|
|
||||||
new_ipv4_address = Some(Ipv4Address::from_bytes(&config.ipv4_address));
|
|
||||||
}
|
|
||||||
Err(e) =>
|
|
||||||
error!("unable to load eeprom config: {:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Save => {
|
Ok(SessionOutput::Error(e)) => {
|
||||||
let config = Config::new(&mut channels, ipv4_address);
|
let _ = writeln!(socket, "Command error: {:?}", e);
|
||||||
match config.save(&mut eeprom) {
|
|
||||||
Ok(()) => {},
|
|
||||||
Err(e) =>
|
|
||||||
error!("unable to save eeprom config: {:?}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::Ipv4(address) => {
|
|
||||||
new_ipv4_address = Some(Ipv4Address::from_bytes(&address));
|
|
||||||
}
|
|
||||||
Command::Reset => {
|
|
||||||
for i in 0..CHANNELS {
|
|
||||||
channels.power_down(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
SCB::sys_reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(SessionInput::Error(e)) => {
|
|
||||||
error!("session input: {:?}", e);
|
|
||||||
send_line(&mut socket, b"{ \"error\": \"invalid input\" }");
|
|
||||||
}
|
}
|
||||||
Err(_) =>
|
Err(_) =>
|
||||||
socket.close(),
|
socket.close(),
|
||||||
}
|
}
|
||||||
} else if socket.can_send() {
|
} else if socket.can_send() && socket.send_capacity() - socket.send_queue() > 256 {
|
||||||
if let Some(channel) = session.is_report_pending() {
|
while let Some(channel) = session.is_report_pending() {
|
||||||
if report_to(channel, &mut channels, &mut socket) {
|
let state = &mut channels.channel_state(usize::from(channel));
|
||||||
|
let _ = writeln!(
|
||||||
|
socket, "t={} raw{}=0x{:06X}",
|
||||||
|
state.adc_time, channel, state.adc_data.unwrap_or(0)
|
||||||
|
).map(|_| {
|
||||||
session.mark_report_sent(channel);
|
session.mark_report_sent(channel);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
// Apply new IPv4 address
|
}
|
||||||
new_ipv4_address.map(|new_ipv4_address| {
|
|
||||||
server.set_ipv4_address(ipv4_address);
|
|
||||||
ipv4_address = new_ipv4_address;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update watchdog
|
// Update watchdog
|
||||||
wd.feed();
|
wd.feed();
|
||||||
|
|
||||||
leds.g4.off();
|
|
||||||
cortex_m::interrupt::free(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
if !net::is_pending(cs) {
|
if !net::is_pending(cs) {
|
||||||
// Wait for interrupts
|
// Wait for interrupts
|
||||||
// (Ethernet, SysTick, or USB)
|
// (Ethernet or SysTick)
|
||||||
wfi();
|
wfi();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
leds.g4.on();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
27
src/net.rs
27
src/net.rs
@ -2,15 +2,14 @@
|
|||||||
//! declared once and globally.
|
//! declared once and globally.
|
||||||
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use cortex_m::interrupt::{CriticalSection, Mutex};
|
use cortex_m::interrupt::Mutex;
|
||||||
|
use bare_metal::CriticalSection;
|
||||||
use stm32f4xx_hal::{
|
use stm32f4xx_hal::{
|
||||||
rcc::Clocks,
|
|
||||||
stm32::{interrupt, Peripherals, ETHERNET_MAC, ETHERNET_DMA},
|
stm32::{interrupt, Peripherals, ETHERNET_MAC, ETHERNET_DMA},
|
||||||
};
|
};
|
||||||
use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Address};
|
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, EthernetInterface};
|
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, EthernetInterface};
|
||||||
use stm32_eth::{Eth, RingEntry, PhyAddress, RxDescriptor, TxDescriptor};
|
use stm32_eth::{Eth, RingEntry, RxDescriptor, TxDescriptor};
|
||||||
use crate::pins::EthernetPins;
|
|
||||||
|
|
||||||
/// Not on the stack so that stack can be placed in CCMRAM (which the
|
/// Not on the stack so that stack can be placed in CCMRAM (which the
|
||||||
/// ethernet peripheral cannot access)
|
/// ethernet peripheral cannot access)
|
||||||
@ -25,12 +24,8 @@ static NET_PENDING: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
|
|||||||
|
|
||||||
/// Run callback `f` with ethernet driver and TCP/IP stack
|
/// Run callback `f` with ethernet driver and TCP/IP stack
|
||||||
pub fn run<F>(
|
pub fn run<F>(
|
||||||
clocks: Clocks,
|
|
||||||
ethernet_mac: ETHERNET_MAC, ethernet_dma: ETHERNET_DMA,
|
ethernet_mac: ETHERNET_MAC, ethernet_dma: ETHERNET_DMA,
|
||||||
eth_pins: EthernetPins,
|
ethernet_addr: EthernetAddress, f: F
|
||||||
ethernet_addr: EthernetAddress,
|
|
||||||
local_addr: Ipv4Address,
|
|
||||||
f: F
|
|
||||||
) where
|
) where
|
||||||
F: FnOnce(EthernetInterface<&mut stm32_eth::Eth<'static, 'static>>),
|
F: FnOnce(EthernetInterface<&mut stm32_eth::Eth<'static, 'static>>),
|
||||||
{
|
{
|
||||||
@ -43,17 +38,13 @@ pub fn run<F>(
|
|||||||
// Ethernet driver
|
// Ethernet driver
|
||||||
let mut eth_dev = Eth::new(
|
let mut eth_dev = Eth::new(
|
||||||
ethernet_mac, ethernet_dma,
|
ethernet_mac, ethernet_dma,
|
||||||
&mut rx_ring[..], &mut tx_ring[..],
|
&mut rx_ring[..], &mut tx_ring[..]
|
||||||
PhyAddress::_0,
|
);
|
||||||
clocks,
|
|
||||||
eth_pins,
|
|
||||||
).unwrap();
|
|
||||||
eth_dev.enable_interrupt();
|
eth_dev.enable_interrupt();
|
||||||
|
|
||||||
// IP stack
|
// IP stack
|
||||||
// Netmask 0 means we expect any IP address on the local segment.
|
let local_addr = IpAddress::v4(192, 168, 1, 26);
|
||||||
// No routing.
|
let mut ip_addrs = [IpCidr::new(local_addr, 24)];
|
||||||
let mut ip_addrs = [IpCidr::new(local_addr.into(), 0)];
|
|
||||||
let mut neighbor_storage = [None; 16];
|
let mut neighbor_storage = [None; 16];
|
||||||
let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]);
|
let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]);
|
||||||
let iface = EthernetInterfaceBuilder::new(&mut eth_dev)
|
let iface = EthernetInterfaceBuilder::new(&mut eth_dev)
|
||||||
|
106
src/pid.rs
106
src/pid.rs
@ -1,26 +1,24 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
#[derive(Clone, Copy)]
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
pub kp: f32,
|
pub kp: f64,
|
||||||
pub ki: f32,
|
pub ki: f64,
|
||||||
pub kd: f32,
|
pub kd: f64,
|
||||||
pub output_min: f32,
|
pub output_min: f64,
|
||||||
pub output_max: f32,
|
pub output_max: f64,
|
||||||
pub integral_min: f32,
|
pub integral_min: f64,
|
||||||
pub integral_max: f32
|
pub integral_max: f64
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Parameters {
|
impl Default for Parameters {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Parameters {
|
Parameters {
|
||||||
kp: 1.5,
|
kp: 0.5,
|
||||||
ki: 0.1,
|
ki: 0.05,
|
||||||
kd: 150.0,
|
kd: 0.45,
|
||||||
output_min: 0.0,
|
output_min: 0.0,
|
||||||
output_max: 2.0,
|
output_max: 5.0,
|
||||||
integral_min: -10.0,
|
integral_min: 0.0,
|
||||||
integral_max: 10.0,
|
integral_max: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,64 +44,40 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, input: f64) -> f64 {
|
pub fn update(&mut self, input: f64) -> f64 {
|
||||||
// error
|
let error = self.target - input;
|
||||||
let error = input - self.target;
|
|
||||||
|
|
||||||
// partial
|
let p = self.parameters.kp * error;
|
||||||
let p = f64::from(self.parameters.kp) * error;
|
|
||||||
|
|
||||||
// integral
|
self.integral += error;
|
||||||
self.integral += f64::from(self.parameters.ki) * error;
|
if self.integral < self.parameters.integral_min {
|
||||||
if self.integral < self.parameters.integral_min.into() {
|
self.integral = self.parameters.integral_min;
|
||||||
self.integral = self.parameters.integral_min.into();
|
|
||||||
}
|
}
|
||||||
if self.integral > self.parameters.integral_max.into() {
|
if self.integral > self.parameters.integral_max {
|
||||||
self.integral = self.parameters.integral_max.into();
|
self.integral = self.parameters.integral_max;
|
||||||
}
|
}
|
||||||
let i = self.integral;
|
let i = self.parameters.ki * self.integral;
|
||||||
|
|
||||||
// derivative
|
|
||||||
let d = match self.last_input {
|
let d = match self.last_input {
|
||||||
None => 0.0,
|
None => 0.0,
|
||||||
Some(last_input) => f64::from(self.parameters.kd) * (input - last_input),
|
Some(last_input) => self.parameters.kd * (last_input - input)
|
||||||
};
|
};
|
||||||
self.last_input = Some(input);
|
self.last_input = Some(input);
|
||||||
|
|
||||||
// output
|
|
||||||
let mut output = p + i + d;
|
let mut output = p + i + d;
|
||||||
if output < self.parameters.output_min.into() {
|
if output < self.parameters.output_min {
|
||||||
output = self.parameters.output_min.into();
|
output = self.parameters.output_min;
|
||||||
}
|
}
|
||||||
if output > self.parameters.output_max.into() {
|
if output > self.parameters.output_max {
|
||||||
output = self.parameters.output_max.into();
|
output = self.parameters.output_max;
|
||||||
}
|
}
|
||||||
self.last_output = Some(output);
|
self.last_output = Some(output);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn summary(&self, channel: usize) -> Summary {
|
#[allow(dead_code)]
|
||||||
Summary {
|
pub fn reset(&mut self) {
|
||||||
channel,
|
self.integral = 0.0;
|
||||||
parameters: self.parameters.clone(),
|
self.last_input = None;
|
||||||
target: self.target,
|
|
||||||
integral: self.integral,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U360>;
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Summary {
|
|
||||||
channel: usize,
|
|
||||||
parameters: Parameters,
|
|
||||||
target: f64,
|
|
||||||
integral: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Summary {
|
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
|
||||||
serde_json_core::to_vec(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,12 +98,12 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_controller() {
|
fn test_controller() {
|
||||||
const DEFAULT: f64 = 0.0;
|
const DEFAULT: f64 = 0.0;
|
||||||
const TARGET: f64 = -1234.56;
|
const TARGET: f64 = 1234.56;
|
||||||
const ERROR: f64 = 0.01;
|
const ERROR: f64 = 0.01;
|
||||||
const DELAY: usize = 10;
|
const DELAY: usize = 10;
|
||||||
|
|
||||||
let mut pid = Controller::new(PARAMETERS.clone());
|
let mut pid = Controller::new(PARAMETERS.clone());
|
||||||
pid.target = TARGET;
|
pid.set_target(TARGET);
|
||||||
|
|
||||||
let mut values = [DEFAULT; DELAY];
|
let mut values = [DEFAULT; DELAY];
|
||||||
let mut t = 0;
|
let mut t = 0;
|
||||||
@ -139,19 +113,11 @@ mod test {
|
|||||||
let next_t = (t + 1) % DELAY;
|
let next_t = (t + 1) % DELAY;
|
||||||
// Feed the oldest temperature
|
// Feed the oldest temperature
|
||||||
let output = pid.update(values[next_t]);
|
let output = pid.update(values[next_t]);
|
||||||
// Overwrite oldest with previous temperature - output
|
// Overwrite oldest with previous temperature + output
|
||||||
values[next_t] = values[t] - output;
|
values[next_t] = values[t] + output;
|
||||||
t = next_t;
|
t = next_t;
|
||||||
total_t += 1;
|
total_t += 1;
|
||||||
}
|
}
|
||||||
}
|
dbg!(values[t], total_t);
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn summary_to_json() {
|
|
||||||
let mut pid = Controller::new(PARAMETERS.clone());
|
|
||||||
pid.target = 30.0 / 1.1;
|
|
||||||
let buf = pid.summary(0).to_json().unwrap();
|
|
||||||
assert_eq!(buf[0], b'{');
|
|
||||||
assert_eq!(buf[buf.len() - 1], b'}');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
194
src/pins.rs
194
src/pins.rs
@ -1,7 +1,8 @@
|
|||||||
use stm32f4xx_hal::{
|
use stm32f4xx_hal::{
|
||||||
adc::Adc,
|
adc::Adc,
|
||||||
|
hal::{blocking::spi::Transfer, digital::v2::{InputPin, OutputPin}},
|
||||||
gpio::{
|
gpio::{
|
||||||
AF5, Alternate, AlternateOD, Analog, Floating, Input,
|
AF5, Alternate, Analog,
|
||||||
gpioa::*,
|
gpioa::*,
|
||||||
gpiob::*,
|
gpiob::*,
|
||||||
gpioc::*,
|
gpioc::*,
|
||||||
@ -10,55 +11,36 @@ use stm32f4xx_hal::{
|
|||||||
gpiog::*,
|
gpiog::*,
|
||||||
GpioExt,
|
GpioExt,
|
||||||
Output, PushPull,
|
Output, PushPull,
|
||||||
|
Speed::VeryHigh,
|
||||||
},
|
},
|
||||||
hal::{self, blocking::spi::Transfer, digital::v2::OutputPin},
|
|
||||||
i2c::I2c,
|
|
||||||
otg_fs::USB,
|
|
||||||
rcc::Clocks,
|
rcc::Clocks,
|
||||||
pwm::{self, PwmChannels},
|
pwm::{self, PwmChannels},
|
||||||
spi::{Spi, NoMiso},
|
spi::{Spi, NoMiso},
|
||||||
stm32::{
|
stm32::{ADC1, ADC2, ADC3, GPIOA, GPIOB, GPIOC, GPIOE, GPIOF, GPIOG, SPI2, SPI4, SPI5, TIM1, TIM3},
|
||||||
ADC1,
|
|
||||||
GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG,
|
|
||||||
I2C1,
|
|
||||||
OTG_FS_GLOBAL, OTG_FS_DEVICE, OTG_FS_PWRCLK,
|
|
||||||
SPI2, SPI4, SPI5,
|
|
||||||
TIM1, TIM3,
|
|
||||||
},
|
|
||||||
time::U32Ext,
|
time::U32Ext,
|
||||||
};
|
};
|
||||||
use eeprom24x::{self, Eeprom24x};
|
use crate::channel::{Channel0, Channel1};
|
||||||
use stm32_eth::EthPins;
|
use crate::softspi::SoftSpi;
|
||||||
use crate::{
|
|
||||||
channel::{Channel0, Channel1},
|
|
||||||
leds::Leds,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Eeprom = Eeprom24x<
|
|
||||||
I2c<I2C1, (
|
|
||||||
PB8<AlternateOD<stm32f4xx_hal::gpio::AF4>>,
|
|
||||||
PB9<AlternateOD<stm32f4xx_hal::gpio::AF4>>
|
|
||||||
)>,
|
|
||||||
eeprom24x::page_size::B8,
|
|
||||||
eeprom24x::addr_size::OneByte
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub type EthernetPins = EthPins<
|
pub struct DummyInputPin;
|
||||||
PA1<Input<Floating>>,
|
|
||||||
PA2<Input<Floating>>,
|
impl InputPin for DummyInputPin {
|
||||||
PC1<Input<Floating>>,
|
type Error = (); // `Void`
|
||||||
PA7<Input<Floating>>,
|
fn is_high(&self) -> Result<bool, Self::Error> {
|
||||||
PB11<Input<Floating>>,
|
Ok(false)
|
||||||
PG13<Input<Floating>>,
|
}
|
||||||
PB13<Input<Floating>>,
|
fn is_low(&self) -> Result<bool, Self::Error> {
|
||||||
PC4<Input<Floating>>,
|
Ok(true)
|
||||||
PC5<Input<Floating>>,
|
}
|
||||||
>;
|
}
|
||||||
|
|
||||||
|
|
||||||
pub trait ChannelPins {
|
pub trait ChannelPins {
|
||||||
type DacSpi: Transfer<u8>;
|
type DacSpi: Transfer<u8>;
|
||||||
type DacSync: OutputPin;
|
type DacSync: OutputPin;
|
||||||
type Shdn: OutputPin;
|
type Shdn: OutputPin;
|
||||||
|
type Adc;
|
||||||
type VRefPin;
|
type VRefPin;
|
||||||
type ItecPin;
|
type ItecPin;
|
||||||
type DacFeedbackPin;
|
type DacFeedbackPin;
|
||||||
@ -69,6 +51,7 @@ impl ChannelPins for Channel0 {
|
|||||||
type DacSpi = Dac0Spi;
|
type DacSpi = Dac0Spi;
|
||||||
type DacSync = PE4<Output<PushPull>>;
|
type DacSync = PE4<Output<PushPull>>;
|
||||||
type Shdn = PE10<Output<PushPull>>;
|
type Shdn = PE10<Output<PushPull>>;
|
||||||
|
type Adc = Adc<ADC1>;
|
||||||
type VRefPin = PA0<Analog>;
|
type VRefPin = PA0<Analog>;
|
||||||
type ItecPin = PA6<Analog>;
|
type ItecPin = PA6<Analog>;
|
||||||
type DacFeedbackPin = PA4<Analog>;
|
type DacFeedbackPin = PA4<Analog>;
|
||||||
@ -79,6 +62,7 @@ impl ChannelPins for Channel1 {
|
|||||||
type DacSpi = Dac1Spi;
|
type DacSpi = Dac1Spi;
|
||||||
type DacSync = PF6<Output<PushPull>>;
|
type DacSync = PF6<Output<PushPull>>;
|
||||||
type Shdn = PE15<Output<PushPull>>;
|
type Shdn = PE15<Output<PushPull>>;
|
||||||
|
type Adc = Adc<ADC2>;
|
||||||
type VRefPin = PA3<Analog>;
|
type VRefPin = PA3<Analog>;
|
||||||
type ItecPin = PB0<Analog>;
|
type ItecPin = PB0<Analog>;
|
||||||
type DacFeedbackPin = PA5<Analog>;
|
type DacFeedbackPin = PA5<Analog>;
|
||||||
@ -88,14 +72,16 @@ impl ChannelPins for Channel1 {
|
|||||||
/// SPI peripheral used for communication with the ADC
|
/// SPI peripheral used for communication with the ADC
|
||||||
pub type AdcSpi = Spi<SPI2, (PB10<Alternate<AF5>>, PB14<Alternate<AF5>>, PB15<Alternate<AF5>>)>;
|
pub type AdcSpi = Spi<SPI2, (PB10<Alternate<AF5>>, PB14<Alternate<AF5>>, PB15<Alternate<AF5>>)>;
|
||||||
pub type AdcNss = PB12<Output<PushPull>>;
|
pub type AdcNss = PB12<Output<PushPull>>;
|
||||||
type Dac0Spi = Spi<SPI4, (PE2<Alternate<AF5>>, NoMiso, PE6<Alternate<AF5>>)>;
|
type Dac0Spi = SoftSpi<PE2<Output<PushPull>>, PE6<Output<PushPull>>, DummyInputPin>;
|
||||||
type Dac1Spi = Spi<SPI5, (PF7<Alternate<AF5>>, NoMiso, PF9<Alternate<AF5>>)>;
|
type Dac1Spi = SoftSpi<PF7<Output<PushPull>>, PF9<Output<PushPull>>, DummyInputPin>;
|
||||||
pub type PinsAdc = Adc<ADC1>;
|
|
||||||
|
pub type TecUMeasAdc = Adc<ADC3>;
|
||||||
|
|
||||||
pub struct ChannelPinSet<C: ChannelPins> {
|
pub struct ChannelPinSet<C: ChannelPins> {
|
||||||
pub dac_spi: C::DacSpi,
|
pub dac_spi: C::DacSpi,
|
||||||
pub dac_sync: C::DacSync,
|
pub dac_sync: C::DacSync,
|
||||||
pub shdn: C::Shdn,
|
pub shdn: C::Shdn,
|
||||||
|
pub adc: C::Adc,
|
||||||
pub vref_pin: C::VRefPin,
|
pub vref_pin: C::VRefPin,
|
||||||
pub itec_pin: C::ItecPin,
|
pub itec_pin: C::ItecPin,
|
||||||
pub dac_feedback_pin: C::DacFeedbackPin,
|
pub dac_feedback_pin: C::DacFeedbackPin,
|
||||||
@ -105,7 +91,7 @@ pub struct ChannelPinSet<C: ChannelPins> {
|
|||||||
pub struct Pins {
|
pub struct Pins {
|
||||||
pub adc_spi: AdcSpi,
|
pub adc_spi: AdcSpi,
|
||||||
pub adc_nss: AdcNss,
|
pub adc_nss: AdcNss,
|
||||||
pub pins_adc: PinsAdc,
|
pub tec_u_meas_adc: TecUMeasAdc,
|
||||||
pub pwm: PwmPins,
|
pub pwm: PwmPins,
|
||||||
pub channel0: ChannelPinSet<Channel0>,
|
pub channel0: ChannelPinSet<Channel0>,
|
||||||
pub channel1: ChannelPinSet<Channel1>,
|
pub channel1: ChannelPinSet<Channel1>,
|
||||||
@ -116,24 +102,26 @@ impl Pins {
|
|||||||
pub fn setup(
|
pub fn setup(
|
||||||
clocks: Clocks,
|
clocks: Clocks,
|
||||||
tim1: TIM1, tim3: TIM3,
|
tim1: TIM1, tim3: TIM3,
|
||||||
gpioa: GPIOA, gpiob: GPIOB, gpioc: GPIOC, gpiod: GPIOD, gpioe: GPIOE, gpiof: GPIOF, gpiog: GPIOG,
|
gpioa: GPIOA, gpiob: GPIOB, gpioc: GPIOC, gpioe: GPIOE, gpiof: GPIOF, gpiog: GPIOG,
|
||||||
i2c1: I2C1,
|
|
||||||
spi2: SPI2, spi4: SPI4, spi5: SPI5,
|
spi2: SPI2, spi4: SPI4, spi5: SPI5,
|
||||||
adc1: ADC1,
|
adc1: ADC1, adc2: ADC2, adc3: ADC3,
|
||||||
otg_fs_global: OTG_FS_GLOBAL, otg_fs_device: OTG_FS_DEVICE, otg_fs_pwrclk: OTG_FS_PWRCLK,
|
) -> Self {
|
||||||
) -> (Self, Leds, Eeprom, EthernetPins, USB) {
|
|
||||||
let gpioa = gpioa.split();
|
let gpioa = gpioa.split();
|
||||||
let gpiob = gpiob.split();
|
let gpiob = gpiob.split();
|
||||||
let gpioc = gpioc.split();
|
let gpioc = gpioc.split();
|
||||||
let gpiod = gpiod.split();
|
|
||||||
let gpioe = gpioe.split();
|
let gpioe = gpioe.split();
|
||||||
let gpiof = gpiof.split();
|
let gpiof = gpiof.split();
|
||||||
let gpiog = gpiog.split();
|
let gpiog = gpiog.split();
|
||||||
|
|
||||||
|
Self::setup_ethernet(
|
||||||
|
gpioa.pa1, gpioa.pa2, gpioc.pc1, gpioa.pa7,
|
||||||
|
gpioc.pc4, gpioc.pc5, gpiob.pb11, gpiog.pg13,
|
||||||
|
gpiob.pb13
|
||||||
|
);
|
||||||
let adc_spi = Self::setup_spi_adc(clocks, spi2, gpiob.pb10, gpiob.pb14, gpiob.pb15);
|
let adc_spi = Self::setup_spi_adc(clocks, spi2, gpiob.pb10, gpiob.pb14, gpiob.pb15);
|
||||||
let adc_nss = gpiob.pb12.into_push_pull_output();
|
let adc_nss = gpiob.pb12.into_push_pull_output();
|
||||||
|
|
||||||
let pins_adc = Adc::adc1(adc1, true, Default::default());
|
let tec_u_meas_adc = Adc::adc3(adc3, true, Default::default());
|
||||||
|
|
||||||
let pwm = PwmPins::setup(
|
let pwm = PwmPins::setup(
|
||||||
clocks, tim1, tim3,
|
clocks, tim1, tim3,
|
||||||
@ -148,6 +136,8 @@ impl Pins {
|
|||||||
);
|
);
|
||||||
let mut shdn0 = gpioe.pe10.into_push_pull_output();
|
let mut shdn0 = gpioe.pe10.into_push_pull_output();
|
||||||
let _ = shdn0.set_low();
|
let _ = shdn0.set_low();
|
||||||
|
let mut adc0 = Adc::adc1(adc1, true, Default::default());
|
||||||
|
adc0.enable();
|
||||||
let vref0_pin = gpioa.pa0.into_analog();
|
let vref0_pin = gpioa.pa0.into_analog();
|
||||||
let itec0_pin = gpioa.pa6.into_analog();
|
let itec0_pin = gpioa.pa6.into_analog();
|
||||||
let dac_feedback0_pin = gpioa.pa4.into_analog();
|
let dac_feedback0_pin = gpioa.pa4.into_analog();
|
||||||
@ -156,6 +146,7 @@ impl Pins {
|
|||||||
dac_spi: dac0_spi,
|
dac_spi: dac0_spi,
|
||||||
dac_sync: dac0_sync,
|
dac_sync: dac0_sync,
|
||||||
shdn: shdn0,
|
shdn: shdn0,
|
||||||
|
adc: adc0,
|
||||||
vref_pin: vref0_pin,
|
vref_pin: vref0_pin,
|
||||||
itec_pin: itec0_pin,
|
itec_pin: itec0_pin,
|
||||||
dac_feedback_pin: dac_feedback0_pin,
|
dac_feedback_pin: dac_feedback0_pin,
|
||||||
@ -168,6 +159,8 @@ impl Pins {
|
|||||||
);
|
);
|
||||||
let mut shdn1 = gpioe.pe15.into_push_pull_output();
|
let mut shdn1 = gpioe.pe15.into_push_pull_output();
|
||||||
let _ = shdn1.set_low();
|
let _ = shdn1.set_low();
|
||||||
|
let mut adc1 = Adc::adc2(adc2, true, Default::default());
|
||||||
|
adc1.enable();
|
||||||
let vref1_pin = gpioa.pa3.into_analog();
|
let vref1_pin = gpioa.pa3.into_analog();
|
||||||
let itec1_pin = gpiob.pb0.into_analog();
|
let itec1_pin = gpiob.pb0.into_analog();
|
||||||
let dac_feedback1_pin = gpioa.pa5.into_analog();
|
let dac_feedback1_pin = gpioa.pa5.into_analog();
|
||||||
@ -176,49 +169,20 @@ impl Pins {
|
|||||||
dac_spi: dac1_spi,
|
dac_spi: dac1_spi,
|
||||||
dac_sync: dac1_sync,
|
dac_sync: dac1_sync,
|
||||||
shdn: shdn1,
|
shdn: shdn1,
|
||||||
|
adc: adc1,
|
||||||
vref_pin: vref1_pin,
|
vref_pin: vref1_pin,
|
||||||
itec_pin: itec1_pin,
|
itec_pin: itec1_pin,
|
||||||
dac_feedback_pin: dac_feedback1_pin,
|
dac_feedback_pin: dac_feedback1_pin,
|
||||||
tec_u_meas_pin: tec_u_meas1_pin,
|
tec_u_meas_pin: tec_u_meas1_pin,
|
||||||
};
|
};
|
||||||
|
|
||||||
let pins = Pins {
|
Pins {
|
||||||
adc_spi, adc_nss,
|
adc_spi, adc_nss,
|
||||||
pins_adc,
|
tec_u_meas_adc,
|
||||||
pwm,
|
pwm,
|
||||||
channel0,
|
channel0,
|
||||||
channel1,
|
channel1,
|
||||||
};
|
}
|
||||||
|
|
||||||
let leds = Leds::new(gpiod.pd9, gpiod.pd10.into_push_pull_output(), gpiod.pd11.into_push_pull_output());
|
|
||||||
|
|
||||||
let eeprom_scl = gpiob.pb8.into_alternate_af4().set_open_drain();
|
|
||||||
let eeprom_sda = gpiob.pb9.into_alternate_af4().set_open_drain();
|
|
||||||
let eeprom_i2c = I2c::i2c1(i2c1, (eeprom_scl, eeprom_sda), 400.khz(), clocks);
|
|
||||||
let eeprom = Eeprom24x::new_24x02(eeprom_i2c, eeprom24x::SlaveAddr::default());
|
|
||||||
|
|
||||||
let eth_pins = EthPins {
|
|
||||||
ref_clk: gpioa.pa1,
|
|
||||||
md_io: gpioa.pa2,
|
|
||||||
md_clk: gpioc.pc1,
|
|
||||||
crs: gpioa.pa7,
|
|
||||||
tx_en: gpiob.pb11,
|
|
||||||
tx_d0: gpiog.pg13,
|
|
||||||
tx_d1: gpiob.pb13,
|
|
||||||
rx_d0: gpioc.pc4,
|
|
||||||
rx_d1: gpioc.pc5,
|
|
||||||
};
|
|
||||||
|
|
||||||
let usb = USB {
|
|
||||||
usb_global: otg_fs_global,
|
|
||||||
usb_device: otg_fs_device,
|
|
||||||
usb_pwrclk: otg_fs_pwrclk,
|
|
||||||
pin_dm: gpioa.pa11.into_alternate_af10(),
|
|
||||||
pin_dp: gpioa.pa12.into_alternate_af10(),
|
|
||||||
hclk: clocks.hclk(),
|
|
||||||
};
|
|
||||||
|
|
||||||
(pins, leds, eeprom, eth_pins, usb)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the GPIO pins for SPI operation, and initialize SPI
|
/// Configure the GPIO pins for SPI operation, and initialize SPI
|
||||||
@ -245,15 +209,11 @@ impl Pins {
|
|||||||
fn setup_dac0<M1, M2, M3>(
|
fn setup_dac0<M1, M2, M3>(
|
||||||
clocks: Clocks, spi4: SPI4,
|
clocks: Clocks, spi4: SPI4,
|
||||||
sclk: PE2<M1>, sync: PE4<M2>, sdin: PE6<M3>
|
sclk: PE2<M1>, sync: PE4<M2>, sdin: PE6<M3>
|
||||||
) -> (Dac0Spi, <Channel0 as ChannelPins>::DacSync) {
|
) -> (Dac0Spi, PE4<Output<PushPull>>) {
|
||||||
let sclk = sclk.into_alternate_af5();
|
let sclk = sclk.into_push_pull_output();
|
||||||
let sdin = sdin.into_alternate_af5();
|
let sdin = sdin.into_push_pull_output();
|
||||||
let spi = Spi::spi4(
|
let spi = SoftSpi::new(
|
||||||
spi4,
|
sclk, sdin, DummyInputPin,
|
||||||
(sclk, NoMiso, sdin),
|
|
||||||
crate::ad5680::SPI_MODE,
|
|
||||||
crate::ad5680::SPI_CLOCK.into(),
|
|
||||||
clocks
|
|
||||||
);
|
);
|
||||||
let sync = sync.into_push_pull_output();
|
let sync = sync.into_push_pull_output();
|
||||||
|
|
||||||
@ -263,20 +223,42 @@ impl Pins {
|
|||||||
fn setup_dac1<M1, M2, M3>(
|
fn setup_dac1<M1, M2, M3>(
|
||||||
clocks: Clocks, spi5: SPI5,
|
clocks: Clocks, spi5: SPI5,
|
||||||
sclk: PF7<M1>, sync: PF6<M2>, sdin: PF9<M3>
|
sclk: PF7<M1>, sync: PF6<M2>, sdin: PF9<M3>
|
||||||
) -> (Dac1Spi, <Channel1 as ChannelPins>::DacSync) {
|
) -> (Dac1Spi, PF6<Output<PushPull>>) {
|
||||||
let sclk = sclk.into_alternate_af5();
|
let sclk = sclk.into_push_pull_output();
|
||||||
let sdin = sdin.into_alternate_af5();
|
let sdin = sdin.into_push_pull_output();
|
||||||
let spi = Spi::spi5(
|
let spi = SoftSpi::new(
|
||||||
spi5,
|
sclk, sdin, DummyInputPin,
|
||||||
(sclk, NoMiso, sdin),
|
|
||||||
crate::ad5680::SPI_MODE,
|
|
||||||
crate::ad5680::SPI_CLOCK.into(),
|
|
||||||
clocks
|
|
||||||
);
|
);
|
||||||
let sync = sync.into_push_pull_output();
|
let sync = sync.into_push_pull_output();
|
||||||
|
|
||||||
(spi, sync)
|
(spi, sync)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the GPIO pins for Ethernet operation
|
||||||
|
fn setup_ethernet<M1, M2, M3, M4, M5, M6, M7, M8, M9>(
|
||||||
|
pa1: PA1<M1>, pa2: PA2<M2>, pc1: PC1<M3>, pa7: PA7<M4>,
|
||||||
|
pc4: PC4<M5>, pc5: PC5<M6>, pb11: PB11<M7>, pg13: PG13<M8>,
|
||||||
|
pb13: PB13<M9>
|
||||||
|
) {
|
||||||
|
// PA1 RMII Reference Clock - SB13 ON
|
||||||
|
pa1.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PA2 RMII MDIO - SB160 ON
|
||||||
|
pa2.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PC1 RMII MDC - SB164 ON
|
||||||
|
pc1.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PA7 RMII RX Data Valid D11 JP6 ON
|
||||||
|
pa7.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PC4 RMII RXD0 - SB178 ON
|
||||||
|
pc4.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PC5 RMII RXD1 - SB181 ON
|
||||||
|
pc5.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PB11 RMII TX Enable - SB183 ON
|
||||||
|
pb11.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PG13 RXII TXD0 - SB182 ON
|
||||||
|
pg13.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
// PB13 RMII TXD1 I2S_A_CK JP7 ON
|
||||||
|
pb13.into_alternate_af11().set_speed(VeryHigh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PwmPins {
|
pub struct PwmPins {
|
||||||
@ -302,17 +284,11 @@ impl PwmPins {
|
|||||||
) -> PwmPins {
|
) -> PwmPins {
|
||||||
let freq = 20u32.khz();
|
let freq = 20u32.khz();
|
||||||
|
|
||||||
fn init_pwm_pin<P: hal::PwmPin<Duty=u16>>(pin: &mut P) {
|
|
||||||
pin.set_duty(0);
|
|
||||||
pin.enable();
|
|
||||||
}
|
|
||||||
let channels = (
|
let channels = (
|
||||||
max_v0.into_alternate_af2(),
|
max_v0.into_alternate_af2(),
|
||||||
max_v1.into_alternate_af2(),
|
max_v1.into_alternate_af2(),
|
||||||
);
|
);
|
||||||
let (mut max_v0, mut max_v1) = pwm::tim3(tim3, channels, clocks, freq);
|
let (max_v0, max_v1) = pwm::tim3(tim3, channels, clocks, freq);
|
||||||
init_pwm_pin(&mut max_v0);
|
|
||||||
init_pwm_pin(&mut max_v1);
|
|
||||||
|
|
||||||
let channels = (
|
let channels = (
|
||||||
max_i_pos0.into_alternate_af1(),
|
max_i_pos0.into_alternate_af1(),
|
||||||
@ -320,12 +296,8 @@ impl PwmPins {
|
|||||||
max_i_neg0.into_alternate_af1(),
|
max_i_neg0.into_alternate_af1(),
|
||||||
max_i_neg1.into_alternate_af1(),
|
max_i_neg1.into_alternate_af1(),
|
||||||
);
|
);
|
||||||
let (mut max_i_pos0, mut max_i_pos1, mut max_i_neg0, mut max_i_neg1) =
|
let (max_i_pos0, max_i_pos1, max_i_neg0, max_i_neg1) =
|
||||||
pwm::tim1(tim1, channels, clocks, freq);
|
pwm::tim1(tim1, channels, clocks, freq);
|
||||||
init_pwm_pin(&mut max_i_pos0);
|
|
||||||
init_pwm_pin(&mut max_i_neg0);
|
|
||||||
init_pwm_pin(&mut max_i_pos1);
|
|
||||||
init_pwm_pin(&mut max_i_neg1);
|
|
||||||
|
|
||||||
PwmPins {
|
PwmPins {
|
||||||
max_v0, max_v1,
|
max_v0, max_v1,
|
||||||
|
@ -3,7 +3,6 @@ use smoltcp::{
|
|||||||
iface::EthernetInterface,
|
iface::EthernetInterface,
|
||||||
socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef},
|
socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
wire::{IpCidr, Ipv4Address, Ipv4Cidr},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -84,21 +83,4 @@ impl<'a, 'b, S: Default> Server<'a, 'b, S> {
|
|||||||
callback(socket, &mut state.state);
|
callback(socket, &mut state.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_ipv4_address(&mut self, ipv4_address: Ipv4Address) {
|
|
||||||
self.net.update_ip_addrs(|addrs| {
|
|
||||||
for addr in addrs.iter_mut() {
|
|
||||||
match addr {
|
|
||||||
IpCidr::Ipv4(_) => {
|
|
||||||
*addr = IpCidr::Ipv4(Ipv4Cidr::new(ipv4_address, 0));
|
|
||||||
// done
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -38,16 +38,16 @@ impl LineReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SessionInput {
|
pub enum SessionOutput {
|
||||||
Nothing,
|
Nothing,
|
||||||
Command(Command),
|
Command(Command),
|
||||||
Error(ParserError),
|
Error(ParserError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Result<Command, ParserError>> for SessionInput {
|
impl From<Result<Command, ParserError>> for SessionOutput {
|
||||||
fn from(input: Result<Command, ParserError>) -> Self {
|
fn from(input: Result<Command, ParserError>) -> Self {
|
||||||
input.map(SessionInput::Command)
|
input.map(SessionOutput::Command)
|
||||||
.unwrap_or_else(SessionInput::Error)
|
.unwrap_or_else(SessionOutput::Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ impl Session {
|
|||||||
self.report_pending[channel] = false;
|
self.report_pending[channel] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionInput) {
|
pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionOutput) {
|
||||||
let mut buf_bytes = 0;
|
let mut buf_bytes = 0;
|
||||||
for (i, b) in buf.iter().enumerate() {
|
for (i, b) in buf.iter().enumerate() {
|
||||||
buf_bytes = i + 1;
|
buf_bytes = i + 1;
|
||||||
@ -125,6 +125,6 @@ impl Session {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(buf_bytes, SessionInput::Nothing)
|
(buf_bytes, SessionOutput::Nothing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
src/softspi.rs
Normal file
147
src/softspi.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use stm32f4xx_hal::hal::{
|
||||||
|
spi::FullDuplex,
|
||||||
|
digital::v2::{InputPin, OutputPin},
|
||||||
|
blocking::spi::{Transfer, transfer},
|
||||||
|
};
|
||||||
|
use nb::{block, Error, Error::WouldBlock};
|
||||||
|
use crate::timer::now;
|
||||||
|
|
||||||
|
/// Bit-banged Mode3 SPI
|
||||||
|
pub struct SoftSpi<SCK, MOSI, MISO> {
|
||||||
|
sck: SCK,
|
||||||
|
mosi: MOSI,
|
||||||
|
miso: MISO,
|
||||||
|
state: State,
|
||||||
|
input: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum State {
|
||||||
|
Idle,
|
||||||
|
Transfer {
|
||||||
|
clock_phase: bool,
|
||||||
|
mask: u8,
|
||||||
|
output: u8,
|
||||||
|
input: u8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> SoftSpi<SCK, MOSI, MISO> {
|
||||||
|
pub fn new(mut sck: SCK, mut mosi: MOSI, miso: MISO) -> Self {
|
||||||
|
let _ = sck.set_high();
|
||||||
|
let _ = mosi.set_low();
|
||||||
|
SoftSpi {
|
||||||
|
sck, mosi, miso,
|
||||||
|
state: State::Idle,
|
||||||
|
input: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this at twice the data rate
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
match self.state {
|
||||||
|
State::Idle => {}
|
||||||
|
State::Transfer { clock_phase: false,
|
||||||
|
mask, output, input } => {
|
||||||
|
if output & mask != 0 {
|
||||||
|
let _ = self.mosi.set_high();
|
||||||
|
} else {
|
||||||
|
let _ = self.mosi.set_low();
|
||||||
|
}
|
||||||
|
let _ = self.sck.set_low();
|
||||||
|
|
||||||
|
self.state = State::Transfer {
|
||||||
|
clock_phase: true,
|
||||||
|
mask, output, input,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
State::Transfer { clock_phase: true,
|
||||||
|
mask, output, mut input } => {
|
||||||
|
if self.miso.is_high().unwrap_or(false) {
|
||||||
|
input |= mask;
|
||||||
|
}
|
||||||
|
let _ = self.sck.set_high();
|
||||||
|
|
||||||
|
if mask != 1 {
|
||||||
|
self.state = State::Transfer {
|
||||||
|
clock_phase: false,
|
||||||
|
mask: mask >> 1,
|
||||||
|
output, input,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.input = Some(input);
|
||||||
|
self.state = State::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
while self.state != State::Idle {
|
||||||
|
self.tick();
|
||||||
|
spi_delay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retry<R, E, F>(&mut self, f: &F) -> Result<R, E>
|
||||||
|
where
|
||||||
|
F: Fn(&'_ mut SoftSpi<SCK, MOSI, MISO>) -> Result<R, nb::Error<E>>
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
match f(self) {
|
||||||
|
Ok(r) => return Ok(r),
|
||||||
|
Err(nb::Error::Other(e)) => return Err(e),
|
||||||
|
Err(WouldBlock) => self.run(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> FullDuplex<u8> for SoftSpi<SCK, MOSI, MISO> {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
|
||||||
|
match self.input.take() {
|
||||||
|
Some(input) =>
|
||||||
|
Ok(input),
|
||||||
|
None if self.state == State::Idle =>
|
||||||
|
Err(nb::Error::Other(())),
|
||||||
|
None =>
|
||||||
|
Err(WouldBlock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&mut self, output: u8) -> Result<(), nb::Error<Self::Error>> {
|
||||||
|
match self.state {
|
||||||
|
State::Idle => {
|
||||||
|
self.state = State::Transfer {
|
||||||
|
clock_phase: false,
|
||||||
|
mask: 0x80,
|
||||||
|
output,
|
||||||
|
input: 0,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> Transfer<u8> for SoftSpi<SCK, MOSI, MISO> {
|
||||||
|
// TODO: proper type
|
||||||
|
type Error = ();
|
||||||
|
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
|
||||||
|
for b in words.iter_mut() {
|
||||||
|
self.retry(&|spi| spi.send(*b))?;
|
||||||
|
*b = self.retry(&|spi| spi.read())?;
|
||||||
|
}
|
||||||
|
Ok(words)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spi_delay() {
|
||||||
|
const DELAY: u32 = 1;
|
||||||
|
|
||||||
|
let start = now();
|
||||||
|
while now() - start < DELAY {}
|
||||||
|
}
|
@ -1,46 +1,31 @@
|
|||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
use uom::si::{
|
|
||||||
f64::{
|
|
||||||
ElectricalResistance,
|
|
||||||
ThermodynamicTemperature,
|
|
||||||
},
|
|
||||||
electrical_resistance::ohm,
|
|
||||||
ratio::ratio,
|
|
||||||
thermodynamic_temperature::{degree_celsius, kelvin},
|
|
||||||
};
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
type JsonBuffer = heapless::Vec<u8, heapless::consts::U200>;
|
|
||||||
|
|
||||||
/// Steinhart-Hart equation parameters
|
/// Steinhart-Hart equation parameters
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
/// Base temperature
|
pub t0: f64,
|
||||||
pub t0: ThermodynamicTemperature,
|
|
||||||
/// Base resistance
|
|
||||||
pub r0: ElectricalResistance,
|
|
||||||
/// Beta
|
|
||||||
pub b: f64,
|
pub b: f64,
|
||||||
|
pub r0: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameters {
|
impl Parameters {
|
||||||
/// Perform the voltage to temperature conversion.
|
/// Perform the voltage to temperature conversion.
|
||||||
pub fn get_temperature(&self, r: ElectricalResistance) -> ThermodynamicTemperature {
|
///
|
||||||
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b;
|
/// Result unit: Kelvin
|
||||||
ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp)
|
///
|
||||||
}
|
/// TODO: verify
|
||||||
|
pub fn get_temperature(&self, r: f64) -> f64 {
|
||||||
pub fn to_json(&self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
let inv_temp = 1.0 / self.t0 + (r / self.r0).ln() / self.b;
|
||||||
serde_json_core::to_vec(self)
|
1.0 / inv_temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Parameters {
|
impl Default for Parameters {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Parameters {
|
Parameters {
|
||||||
t0: ThermodynamicTemperature::new::<degree_celsius>(25.0),
|
t0: 0.001_4,
|
||||||
r0: ElectricalResistance::new::<ohm>(10_000.0),
|
b: 0.000_000_099,
|
||||||
b: 3800.0,
|
r0: 5_110.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,3 @@ pub fn now() -> u32 {
|
|||||||
.deref()
|
.deref()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// block for at least `amount` milliseconds
|
|
||||||
pub fn sleep(amount: u32) {
|
|
||||||
let start = now();
|
|
||||||
while now() - start <= amount {}
|
|
||||||
}
|
|
||||||
|
65
src/units.rs
Normal file
65
src/units.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
ops::{Add, Div, Neg, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! impl_add_sub {
|
||||||
|
($Type: ident) => {
|
||||||
|
impl Add<$Type> for $Type {
|
||||||
|
type Output = $Type;
|
||||||
|
fn add(self, rhs: $Type) -> $Type {
|
||||||
|
$Type(self.0 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<$Type> for $Type {
|
||||||
|
type Output = $Type;
|
||||||
|
fn sub(self, rhs: $Type) -> $Type {
|
||||||
|
$Type(self.0 - rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for $Type {
|
||||||
|
type Output = $Type;
|
||||||
|
fn neg(self) -> $Type {
|
||||||
|
$Type(-self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
pub struct Volts(pub f64);
|
||||||
|
impl_add_sub!(Volts);
|
||||||
|
|
||||||
|
impl fmt::Display for Volts {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:.3}V", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<Ohms> for Volts {
|
||||||
|
type Output = Amps;
|
||||||
|
fn div(self, rhs: Ohms) -> Amps {
|
||||||
|
Amps(self.0 / rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
pub struct Amps(pub f64);
|
||||||
|
impl_add_sub!(Amps);
|
||||||
|
|
||||||
|
impl fmt::Display for Amps {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:.3}A", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
pub struct Ohms(pub f64);
|
||||||
|
impl_add_sub!(Ohms);
|
||||||
|
|
||||||
|
impl fmt::Display for Ohms {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:.3}Ω", self.0)
|
||||||
|
}
|
||||||
|
}
|
103
src/usb.rs
103
src/usb.rs
@ -1,103 +0,0 @@
|
|||||||
use core::{fmt::{self, Write}, mem::MaybeUninit};
|
|
||||||
use cortex_m::interrupt::free;
|
|
||||||
use stm32f4xx_hal::{
|
|
||||||
otg_fs::{USB, UsbBus as Bus},
|
|
||||||
stm32::{interrupt, Interrupt, NVIC},
|
|
||||||
};
|
|
||||||
use usb_device::{
|
|
||||||
class_prelude::{UsbBusAllocator},
|
|
||||||
prelude::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
|
|
||||||
};
|
|
||||||
use usbd_serial::SerialPort;
|
|
||||||
use log::{Record, Log, Metadata};
|
|
||||||
|
|
||||||
static mut EP_MEMORY: [u32; 1024] = [0; 1024];
|
|
||||||
|
|
||||||
static mut BUS: MaybeUninit<UsbBusAllocator<Bus<USB>>> = MaybeUninit::uninit();
|
|
||||||
// static mut SERIAL_DEV: Option<(SerialPort<'static, Bus<USB>>, UsbDevice<'static, Bus<USB>>)> = None;
|
|
||||||
static mut STATE: Option<State> = None;
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
serial: SerialPort<'static, Bus<USB>>,
|
|
||||||
dev: UsbDevice<'static, Bus<USB>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn setup(usb: USB) {
|
|
||||||
unsafe { BUS.write(Bus::new(usb, &mut EP_MEMORY)) };
|
|
||||||
|
|
||||||
let bus = unsafe { BUS.assume_init_ref() };
|
|
||||||
let serial = SerialPort::new(bus);
|
|
||||||
let dev = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
|
|
||||||
.manufacturer("M-Labs")
|
|
||||||
.product("thermostat")
|
|
||||||
.device_release(0x20)
|
|
||||||
.self_powered(true)
|
|
||||||
.device_class(usbd_serial::USB_CLASS_CDC)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
free(|_| {
|
|
||||||
unsafe { STATE = Some(State { serial, dev }); }
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
NVIC::unmask(Interrupt::OTG_FS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get() -> Option<&'static mut Self> {
|
|
||||||
unsafe { STATE.as_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll() {
|
|
||||||
if let Some(ref mut s) = Self::get() {
|
|
||||||
if s.dev.poll(&mut [&mut s.serial]) {
|
|
||||||
// discard any input
|
|
||||||
let mut buf = [0u8; 64];
|
|
||||||
let _ = s.serial.read(&mut buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
fn OTG_FS() {
|
|
||||||
free(|_| {
|
|
||||||
State::poll();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Logger;
|
|
||||||
|
|
||||||
impl Log for Logger {
|
|
||||||
fn enabled(&self, _: &Metadata) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &Record) {
|
|
||||||
if self.enabled(record.metadata()) {
|
|
||||||
let mut output = SerialOutput;
|
|
||||||
let _ = writeln!(&mut output, "{} - {}", record.level(), record.args());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {
|
|
||||||
if let Some(ref mut state) = State::get() {
|
|
||||||
let _ = free(|_| state.serial.flush());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SerialOutput;
|
|
||||||
|
|
||||||
impl Write for SerialOutput {
|
|
||||||
fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> {
|
|
||||||
if let Some(ref mut state) = State::get() {
|
|
||||||
for chunk in s.as_bytes().chunks(16) {
|
|
||||||
free(|_| state.serial.write(chunk))
|
|
||||||
.map_err(|_| fmt::Error)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user