diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5340aad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "stm32h7xx-hal"] + path = stm32h7xx-hal + url = https://github.com/quartiq/stm32h7xx-hal.git diff --git a/Cargo.lock b/Cargo.lock index c6f11f8..d8ee50f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,391 +1,572 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ad9959" +version = "0.1.0" +dependencies = [ + "bit_field", + "embedded-hal", +] + [[package]] name = "aligned" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" dependencies = [ - "as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "as-slice", ] [[package]] name = "as-slice" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3", + "generic-array 0.13.2", + "stable_deref_trait", +] + +[[package]] +name = "asm-delay" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc4896200a8422e15fbb1899cb436889d215954d376bf32d311a2330ac13440" +dependencies = [ + "bitrate", + "cortex-m", + "embedded-hal", ] [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "bare-metal" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version", ] +[[package]] +name = "bit_field" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitrate" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", +] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cortex-m" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" dependencies = [ - "aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aligned", + "bare-metal", + "volatile-register", ] [[package]] name = "cortex-m-log" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978caafe65d1023d38b00c76b83564788fc351d954a5005fb72cf992c0d61458" dependencies = [ - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m", + "cortex-m-semihosting", + "log", ] [[package]] name = "cortex-m-rt" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" dependencies = [ - "cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt-macros", + "r0", ] [[package]] name = "cortex-m-rt-macros" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" dependencies = [ - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "cortex-m-rtfm" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf0b9fd3f042cb3793d15daf3cea201b2f25c99b0b5b936a551bb6909c3ae5b" dependencies = [ - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-rtfm-macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "heapless 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rtfm-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m", + "cortex-m-rt", + "cortex-m-rtfm-macros", + "heapless", + "rtfm-core", ] [[package]] name = "cortex-m-rtfm-macros" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62092f6ff344e9b0adb748f0302ed69889ba2fae1fce446e3788d4726ea73bb" dependencies = [ - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rtfm-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "rtfm-syntax", + "syn", ] [[package]] name = "cortex-m-semihosting" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" dependencies = [ - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m", +] + +[[package]] +name = "embedded-hal" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b" +dependencies = [ + "nb", + "void", +] + +[[package]] +name = "enum-iterator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "generic-array" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" dependencies = [ - "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", ] [[package]] name = "generic-array" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" dependencies = [ - "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", ] [[package]] name = "hash32" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", ] [[package]] name = "heapless" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0" dependencies = [ - "as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "as-slice", + "generic-array 0.13.2", + "hash32", + "serde", + "stable_deref_trait", ] [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "managed" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" + +[[package]] +name = "mcp23017" +version = "0.1.1" +source = "git+https://github.com/mrd0ll4r/mcp23017.git#a3d072754abca60a92ece820f7cfb767a0c11669" +dependencies = [ + "embedded-hal", +] + +[[package]] +name = "nb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc" [[package]] name = "panic-halt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" + +[[package]] +name = "panic-itm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98830d17a95587207e41edaa3009b143d326ce134b0e3538ac98246a67d66cc3" +dependencies = [ + "cortex-m", +] [[package]] name = "panic-semihosting" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c" dependencies = [ - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m", + "cortex-m-semihosting", ] [[package]] -name = "proc-macro2" -version = "1.0.9" +name = "paste" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", ] [[package]] name = "quote" -version = "1.0.3" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "r0" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" [[package]] name = "rtfm-core" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec893edb2aa5b70320b94896ffea22a7ebb1cf3f942bb67cd5b60a865a63493" [[package]] name = "rtfm-syntax" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4455e23c34df3d66454e7e218a4d76a7f83321d04a806be614463341cec4116e" dependencies = [ - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap", + "proc-macro2", + "syn", ] [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver", ] [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.105" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" dependencies = [ - "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", + "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 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", + "heapless", + "serde", ] [[package]] name = "serde_derive" -version = "1.0.105" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "smoltcp" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "byteorder", + "log", + "managed", ] [[package]] name = "stabilizer" version = "0.3.0" dependencies = [ - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-log 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-rtfm 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "heapless 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "panic-semihosting 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", - "serde-json-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stm32h7 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ad9959", + "asm-delay", + "cortex-m", + "cortex-m-log", + "cortex-m-rt", + "cortex-m-rtfm", + "embedded-hal", + "enum-iterator", + "heapless", + "log", + "mcp23017", + "nb", + "panic-halt", + "panic-semihosting", + "serde", + "serde-json-core", + "smoltcp", + "stm32h7-ethernet", + "stm32h7xx-hal 0.5.0", ] [[package]] name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" [[package]] name = "stm32h7" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9beb5e2a223c82f263c3051bba4614aebc6e98bd40217df3cd8817c83ac7bd8" dependencies = [ - "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bare-metal", + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "stm32h7-ethernet" +version = "0.1.1" +source = "git+https://github.com/quartiq/stm32h7-ethernet.git#cf9b8bb2e1b440d8ada6ac6048f48dc4ed9c269a" +dependencies = [ + "cortex-m", + "log", + "smoltcp", + "stm32h7xx-hal 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stm32h7xx-hal" +version = "0.5.0" +dependencies = [ + "bare-metal", + "cast", + "cortex-m", + "cortex-m-log", + "cortex-m-rt", + "cortex-m-rtfm", + "embedded-hal", + "nb", + "panic-itm", + "paste", + "stm32h7", + "void", +] + +[[package]] +name = "stm32h7xx-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "987c66628f30012ed9a41cc738421c5caece03292c0cc8fd1e99956f122735bd" +dependencies = [ + "bare-metal", + "cast", + "cortex-m", + "cortex-m-rt", + "embedded-hal", + "nb", + "paste", + "stm32h7", + "void", ] [[package]] name = "syn" -version = "1.0.17" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "typenum" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "vcell" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "volatile-register" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" dependencies = [ - "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "vcell", ] - -[metadata] -"checksum aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" -"checksum as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" -"checksum cortex-m-log 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "978caafe65d1023d38b00c76b83564788fc351d954a5005fb72cf992c0d61458" -"checksum cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" -"checksum cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" -"checksum cortex-m-rtfm 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eaf0b9fd3f042cb3793d15daf3cea201b2f25c99b0b5b936a551bb6909c3ae5b" -"checksum cortex-m-rtfm-macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c62092f6ff344e9b0adb748f0302ed69889ba2fae1fce446e3788d4726ea73bb" -"checksum cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" -"checksum hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" -"checksum heapless 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10b591a0032f114b7a77d4fbfab452660c553055515b7d7ece355db080d19087" -"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" -"checksum panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" -"checksum panic-semihosting 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c" -"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" -"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" -"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" -"checksum rtfm-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec893edb2aa5b70320b94896ffea22a7ebb1cf3f942bb67cd5b60a865a63493" -"checksum rtfm-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4455e23c34df3d66454e7e218a4d76a7f83321d04a806be614463341cec4116e" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" -"checksum serde-json-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf406405ada9ef326ca78677324ac66994ff348fc48a16030be08caeed29825" -"checksum serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" -"checksum smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum stm32h7 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b0045066e082648e8a7ab1dd45c92efa8d7bec2beedf72ac7b62563911f82a" -"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" -"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" -"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" diff --git a/Cargo.toml b/Cargo.toml index c62c914..0f43a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,18 @@ -[package] -name = "stabilizer" -version = "0.3.0" -authors = ["Robert Jördens "] -description = "Firmware for the Sinara Stabilizer board (stm32h743, eth, poe, 2 adc, 2 dac)" -categories = ["embedded", "no-std", "hardware-support", "science"] -license = "GPL-3.0-or-later" -keywords = ["ethernet", "stm32h7", "adc", "dac", "physics"] -repository = "https://github.com/quartiq/stabilizer" -readme = "README.md" -documentation = "https://docs.rs/stabilizer/" -edition = "2018" -exclude = [ - ".travis.yml", - ".gitignore", - "doc/", - "doc/*" +[workspace] + +members = [ + "stabilizer", + "stm32h7xx-hal", + "ad9959", ] -[badges] -travis-ci = { repository = "quartiq/stabilizer", branch = "master" } -maintenance = { status = "experimental" } - -[package.metadata.docs.rs] -features = [] -default-target = "thumbv7em-none-eabihf" - -[dependencies] -cortex-m = { version = "0.6", features = ["const-fn"] } -cortex-m-rt = { version = "0.6", features = ["device"] } -cortex-m-log = { version = "0.6", features = ["log-integration"] } -log = "0.4" -panic-semihosting = { version = "0.5", optional = true } -panic-halt = "0.2" -serde = { version = "1.0", features = ["derive"], default-features = false } -heapless = "0.5" -serde-json-core = "0.1" -stm32h7 = { version = "0.10", features = ["stm32h743", "rt"] } -cortex-m-rtfm = "0.5" -smoltcp = { version = "0.6", features = ["ethernet", "proto-ipv4", "socket-tcp"], default-features = false } - -[features] -semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] -bkpt = [ ] -nightly = ["cortex-m/inline-asm"] - [profile.dev] codegen-units = 1 incremental = false +opt-level = 3 [profile.release] +opt-level = 3 debug = true lto = true codegen-units = 1 diff --git a/ad9959/.gitignore b/ad9959/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/ad9959/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/ad9959/Cargo.toml b/ad9959/Cargo.toml new file mode 100644 index 0000000..f234470 --- /dev/null +++ b/ad9959/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ad9959" +version = "0.1.0" +authors = ["Ryan Summers "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +embedded-hal = {version = "0.2.3", features = ["unproven"]} +bit_field = "0.10.0" diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs new file mode 100644 index 0000000..558772d --- /dev/null +++ b/ad9959/src/lib.rs @@ -0,0 +1,480 @@ +#![no_std] + +use bit_field::BitField; +use embedded_hal::{ + digital::v2::OutputPin, + blocking::delay::DelayMs, +}; + +/// A device driver for the AD9959 direct digital synthesis (DDS) chip. +/// +/// This chip provides four independently controllable digital-to-analog output sinusoids with +/// configurable phase, amplitude, and frequency. All channels are inherently synchronized as they +/// are derived off a common system clock. +/// +/// The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz. +/// +/// The chip supports a number of serial interfaces to improve data throughput, including normal, +/// dual, and quad SPI configurations. +pub struct Ad9959 { + interface: INTERFACE, + delay: DELAY, + reference_clock_frequency: f32, + system_clock_multiplier: u8, + io_update: UPDATE, +} + +/// A trait that allows a HAL to provide a means of communicating with the AD9959. +pub trait Interface { + type Error; + + fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>; + + fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>; + + fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>; +} + +/// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to +/// the configuration bits of the DDS CSR register. +#[derive(Copy, Clone, PartialEq)] +pub enum Mode { + SingleBitTwoWire = 0b00, + SingleBitThreeWire = 0b01, + TwoBitSerial = 0b10, + FourBitSerial = 0b11, +} + +/// The configuration registers within the AD9959 DDS device. The values of each register are +/// equivalent to the address. +pub enum Register { + CSR = 0x00, + FR1 = 0x01, + FR2 = 0x02, + CFR = 0x03, + CFTW0 = 0x04, + CPOW0 = 0x05, + ACR = 0x06, + LSRR = 0x07, + RDW = 0x08, + FDW = 0x09, + CW1 = 0x0a, + CW2 = 0x0b, + CW3 = 0x0c, + CW4 = 0x0d, + CW5 = 0x0e, + CW6 = 0x0f, + CW7 = 0x10, + CW8 = 0x11, + CW9 = 0x12, + CW10 = 0x13, + CW11 = 0x14, + CW12 = 0x15, + CW13 = 0x16, + CW14 = 0x17, + CW15 = 0x18, +} + +/// Specifies an output channel of the AD9959 DDS chip. +pub enum Channel { + One = 0, + Two = 1, + Three = 2, + Four = 3, +} + +/// Possible errors generated by the AD9959 driver. +#[derive(Debug)] +pub enum Error { + Interface, + Check, + Bounds, + Pin, + Frequency, +} + +impl Ad9959 +where + INTERFACE: Interface, + DELAY: DelayMs, + UPDATE: OutputPin, + +{ + /// Construct and initialize the DDS. + /// + /// Args: + /// * `interface` - An interface to the DDS. + /// * `reset_pin` - A pin connected to the DDS reset input. + /// * `io_update` - A pin connected to the DDS io_update input. + /// * `delay` - A delay implementation for blocking operation for specific amounts of time. + /// * `desired_mode` - The desired communication mode of the interface to the DDS. + /// * `clock_frequency` - The clock frequency of the reference clock input. + /// * `multiplier` - The desired clock multiplier for the system clock. This multiplies + /// `clock_frequency` to generate the system clock. + pub fn new(interface: INTERFACE, + reset_pin: &mut RST, + io_update: UPDATE, + delay: DELAY, + desired_mode: Mode, + clock_frequency: f32, + multiplier: u8) -> Result + where + RST: OutputPin, + { + let mut ad9959 = Ad9959 { + interface: interface, + io_update: io_update, + delay: delay, + reference_clock_frequency: clock_frequency, + system_clock_multiplier: 1, + }; + + ad9959.io_update.set_low().or_else(|_| Err(Error::Pin))?; + + // Reset the AD9959 + reset_pin.set_high().or_else(|_| Err(Error::Pin))?; + + // Delay for a clock cycle to allow the device to reset. + ad9959.delay.delay_ms((1000.0 / clock_frequency as f32) as u8); + + reset_pin.set_low().or_else(|_| Err(Error::Pin))?; + + ad9959.interface.configure_mode(Mode::SingleBitTwoWire).map_err(|_| Error::Interface)?; + + // Program the interface configuration in the AD9959. Default to all channels enabled. + let mut csr: [u8; 1] = [0xF0]; + csr[0].set_bits(1..3, desired_mode as u8); + ad9959.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + // Latch the configuration registers to make them active. + ad9959.latch_configuration()?; + + ad9959.interface.configure_mode(desired_mode).map_err(|_| Error::Interface)?; + + // Read back the CSR to ensure it specifies the mode correctly. + let mut updated_csr: [u8; 1] = [0]; + ad9959.interface.read(Register::CSR as u8, &mut updated_csr).map_err(|_| Error::Interface)?; + if updated_csr[0] != csr[0] { + return Err(Error::Check); + } + + // Set the clock frequency to configure the device as necessary. + ad9959.configure_system_clock(clock_frequency, multiplier)?; + Ok(ad9959) + } + + /// Latch the DDS configuration to ensure it is active on the output channels. + fn latch_configuration(&mut self) -> Result<(), Error> { + self.io_update.set_high().or_else(|_| Err(Error::Pin))?; + // The SYNC_CLK is 1/4 the system clock frequency. The IO_UPDATE pin must be latched for one + // full SYNC_CLK pulse to register. For safety, we latch for 5 here. + self.delay.delay_ms((5000.0 / self.system_clock_frequency()) as u8); + self.io_update.set_low().or_else(|_| Err(Error::Pin))?; + + Ok(()) + } + + /// Configure the internal system clock of the chip. + /// + /// Arguments: + /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core. + /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20. + /// + /// Returns: + /// The actual frequency configured for the internal system clock. + pub fn configure_system_clock(&mut self, + reference_clock_frequency: f32, + multiplier: u8) -> Result + { + self.reference_clock_frequency = reference_clock_frequency; + + if multiplier != 1 && (multiplier > 20 || multiplier < 4) { + return Err(Error::Bounds); + } + + let frequency = multiplier as f64 * self.reference_clock_frequency as f64; + if frequency > 500_000_000.0f64 { + return Err(Error::Frequency); + } + + // TODO: Update / disable any enabled channels? + let mut fr1: [u8; 3] = [0, 0, 0]; + self.interface.read(Register::FR1 as u8, &mut fr1).map_err(|_| Error::Interface)?; + fr1[0].set_bits(2..=6, multiplier); + + let vco_range = frequency > 255e6; + fr1[0].set_bit(7, vco_range); + + self.interface.write(Register::FR1 as u8, &fr1).map_err(|_| Error::Interface)?; + self.system_clock_multiplier = multiplier; + + Ok(self.system_clock_frequency()) + } + + /// Get the current reference clock frequency in Hz. + pub fn get_reference_clock_frequency(&self) -> f32 { + self.reference_clock_frequency + } + + /// Get the current reference clock multiplier. + pub fn get_reference_clock_multiplier(&mut self) -> Result { + let mut fr1: [u8; 3] = [0, 0, 0]; + self.interface.read(Register::FR1 as u8, &mut fr1).map_err(|_| Error::Interface)?; + + Ok(fr1[0].get_bits(2..=6) as u8) + } + + /// Perform a self-test of the communication interface. + /// + /// Note: + /// This modifies the existing channel enables. They are restored upon exit. + /// + /// Returns: + /// True if the self test succeeded. False otherwise. + pub fn self_test(&mut self) -> Result { + let mut csr: [u8; 1] = [0]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + let old_csr = csr[0]; + + // Enable all channels. + csr[0].set_bits(4..8, 0xF); + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + // Read back the enable. + csr[0] = 0; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + if csr[0].get_bits(4..8) != 0xF { + return Ok(false); + } + + // Clear all channel enables. + csr[0].set_bits(4..8, 0x0); + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + // Read back the enable. + csr[0] = 0xFF; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + if csr[0].get_bits(4..8) != 0 { + return Ok(false); + } + + // Restore the CSR. + csr[0] = old_csr; + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + Ok(true) + } + + /// Get the current system clock frequency in Hz. + fn system_clock_frequency(&self) -> f64 { + self.system_clock_multiplier as f64 * self.reference_clock_frequency as f64 + } + + /// Enable an output channel. + pub fn enable_channel(&mut self, channel: Channel) -> Result<(), Error> { + let mut csr: [u8; 1] = [0]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + csr[0].set_bit(channel as usize + 4, true); + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + Ok(()) + } + + /// Disable an output channel. + pub fn disable_channel(&mut self, channel: Channel) -> Result<(), Error> { + let mut csr: [u8; 1] = [0]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + csr[0].set_bit(channel as usize + 4, false); + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + Ok(()) + } + + /// Determine if an output channel is enabled. + pub fn is_enabled(&mut self, channel: Channel) -> Result { + let mut csr: [u8; 1] = [0; 1]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + + Ok(csr[0].get_bit(channel as usize + 4)) + } + + /// Update an output channel configuration register. + /// + /// Args: + /// * `channel` - The channel to configure. + /// * `register` - The register to update. + /// * `data` - The contents to write to the provided register. + fn modify_channel(&mut self, channel: Channel, register: Register, data: &[u8]) -> Result<(), Error> { + // Disable all other outputs so that we can update the configuration register of only the + // specified channel. + let mut csr: [u8; 1] = [0]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + + let mut new_csr = csr; + new_csr[0].set_bits(4..8, 0); + new_csr[0].set_bit(4 + channel as usize, true); + + self.interface.write(Register::CSR as u8, &new_csr).map_err(|_| Error::Interface)?; + + self.interface.write(register as u8, &data).map_err(|_| Error::Interface)?; + + // Latch the configuration and restore the previous CSR. Note that the re-enable of the + // channel happens immediately, so the CSR update does not need to be latched. + self.latch_configuration()?; + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + Ok(()) + } + + /// Read a configuration register of a specific channel. + /// + /// Args: + /// * `channel` - The channel to read. + /// * `register` - The register to read. + /// * `data` - A location to store the read register contents. + fn read_channel(&mut self, channel: Channel, register: Register, mut data: &mut [u8]) -> Result<(), Error> { + // Disable all other channels in the CSR so that we can read the configuration register of + // only the desired channel. + let mut csr: [u8; 1] = [0]; + self.interface.read(Register::CSR as u8, &mut csr).map_err(|_| Error::Interface)?; + + let mut new_csr = csr; + new_csr[0].set_bits(4..8, 0); + new_csr[0].set_bit(4 + channel as usize, true); + + self.interface.write(Register::CSR as u8, &new_csr).map_err(|_| Error::Interface)?; + + self.interface.read(register as u8, &mut data).map_err(|_| Error::Interface)?; + + // Restore the previous CSR. Note that the re-enable of the channel happens immediately, so + // the CSR update does not need to be latched. + self.interface.write(Register::CSR as u8, &csr).map_err(|_| Error::Interface)?; + + Ok(()) + } + + /// Configure the phase of a specified channel. + /// + /// Arguments: + /// * `channel` - The channel to configure the frequency of. + /// * `phase_turns` - The desired phase offset in turns. + /// + /// Returns: + /// The actual programmed phase offset of the channel in turns. + pub fn set_phase(&mut self, channel: Channel, phase_turns: f32) -> Result { + let phase_offset: u16 = (phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16; + + self.modify_channel(channel, Register::CPOW0, &phase_offset.to_be_bytes())?; + + Ok((phase_offset as f32) / ((1 << 14) as f32)) + } + + /// Get the current phase of a specified channel. + /// + /// Args: + /// * `channel` - The channel to get the phase of. + /// + /// Returns: + /// The phase of the channel in turns. + pub fn get_phase(&mut self, channel: Channel) -> Result { + let mut phase_offset: [u8; 2] = [0; 2]; + self.read_channel(channel, Register::CPOW0, &mut phase_offset)?; + + let phase_offset = u16::from_be_bytes(phase_offset) & 0x3FFFu16; + + Ok((phase_offset as f32) / ((1 << 14) as f32)) + } + + /// Configure the amplitude of a specified channel. + /// + /// Arguments: + /// * `channel` - The channel to configure the frequency of. + /// * `amplitude` - A normalized amplitude setting [0, 1]. + /// + /// Returns: + /// The actual normalized amplitude of the channel relative to full-scale range. + pub fn set_amplitude(&mut self, channel: Channel, amplitude: f32) -> Result { + if amplitude < 0.0 || amplitude > 1.0 { + return Err(Error::Bounds); + } + + let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; + + let mut acr: [u8; 3] = [0; 3]; + + // Enable the amplitude multiplier for the channel if required. The amplitude control has + // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever + // full-scale is used. + if amplitude_control < (1 << 10) { + + let masked_control = amplitude_control & 0x3FF; + acr[1] = masked_control.to_be_bytes()[0]; + acr[2] = masked_control.to_be_bytes()[1]; + + // Enable the amplitude multiplier + acr[1].set_bit(4, true); + } + + self.modify_channel(channel, Register::ACR, &acr)?; + + Ok(amplitude_control as f32 / (1 << 10) as f32) + } + + /// Get the configured amplitude of a channel. + /// + /// Args: + /// * `channel` - The channel to get the amplitude of. + /// + /// Returns: + /// The normalized amplitude of the channel. + pub fn get_amplitude(&mut self, channel: Channel) -> Result { + let mut acr: [u8; 3] = [0; 3]; + self.read_channel(channel, Register::ACR, &mut acr)?; + + if acr[1].get_bit(4) { + let amplitude_control: u16 = (((acr[1] as u16) << 8) | (acr[2] as u16)) & 0x3FF; + Ok(amplitude_control as f32 / (1 << 10) as f32) + } else { + Ok(1.0) + } + } + + /// Configure the frequency of a specified channel. + /// + /// Arguments: + /// * `channel` - The channel to configure the frequency of. + /// * `frequency` - The desired output frequency in Hz. + /// + /// Returns: + /// The actual programmed frequency of the channel. + pub fn set_frequency(&mut self, channel: Channel, frequency: f64) -> Result { + if frequency < 0.0 || frequency > self.system_clock_frequency() { + return Err(Error::Bounds); + } + + // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the + // frequency tuning word and f_s is the system clock rate. + let tuning_word: u32 = ((frequency as f64 / self.system_clock_frequency()) + * 1u64.wrapping_shl(32) as f64) as u32; + + self.modify_channel(channel, Register::CFTW0, &tuning_word.to_be_bytes())?; + Ok((tuning_word as f64 / 1u64.wrapping_shl(32) as f64) * self.system_clock_frequency()) + } + + /// Get the frequency of a channel. + /// + /// Arguments: + /// * `channel` - The channel to get the frequency of. + /// + /// Returns: + /// The frequency of the channel in Hz. + pub fn get_frequency(&mut self, channel: Channel) -> Result { + // Read the frequency tuning word for the channel. + let mut tuning_word: [u8; 4] = [0; 4]; + self.read_channel(channel, Register::CFTW0, &mut tuning_word)?; + let tuning_word = u32::from_be_bytes(tuning_word); + + // Convert the tuning word into a frequency. + Ok(tuning_word as f64 * self.system_clock_frequency() / ((1u64 << 32)) as f64) + } +} diff --git a/openocd.gdb b/openocd.gdb index 1728d7e..e903a33 100644 --- a/openocd.gdb +++ b/openocd.gdb @@ -16,7 +16,7 @@ break rust_begin_unwind load # tbreak cortex_m_rt::reset_handler -# monitor reset halt +monitor reset halt # cycle counter delta tool, place two bkpts around the section set var $cc=0xe0001004 @@ -26,4 +26,3 @@ set var $t0=*$cc continue end #set var $t0=*$cc -continue diff --git a/pounder_test.py b/pounder_test.py new file mode 100644 index 0000000..a50856a --- /dev/null +++ b/pounder_test.py @@ -0,0 +1,111 @@ +#!/usr/bin/python3 +""" +Description: Test Stabilizer communication and DDS configuration. + +Author: Ryan Summers +""" + +import socket +import json + +HOST = '10.0.16.99' +PORT = 1235 + +def do_request(s, request): + """ Perform a request with the Stabilizer. + + Args: + s: The socket to the stabilizer. + request: The request to transmit. + + Returns: + The received response object. + """ + # Transform the value field. + request['value'] = json.dumps(request['value'], separators=[',', ':']).replace('"', "'") + data = (json.dumps(request, separators=[',', ':']) + '\n').encode('ascii') + s.send(data) + + response = b'' + while not response.endswith(b'\n'): + response += s.recv(1024) + + # Decode the value array + response = json.loads(response.decode('ascii')) + response['value'] = response['value'].replace("'", '"') + response['value'] = json.loads(response['value']) + + return response + + +def read_attribute(s, attribute_name): + """ Read an attribute on the Stabilizer device. + + Args: + s: The socket to the stabilizer. + attribute_name: The name of the endpoint to write to (the attribute name). + + Returns: + The value of the attribute. May be a string or a dictionary. + """ + request = { + "req": "Read", + "attribute": attribute_name, + "value": "", + } + + response = do_request(s, request) + + if 'code' not in response or response['code'] != 200: + raise Exception(f'Failed to read {attribute_name}: {response}') + + return response['value'] + + +def write_attribute(s, attribute_name, attribute_value): + """ Write an attribute on the Stabilizer device. + + Args: + s: The socket to the stabilizer. + attribute_name: The name of the endpoint to write to (the attribute name). + attribute_value: The value to write to the attribute. May be a string or a dictionary. + """ + request = { + "req": "Write", + "attribute": attribute_name, + "value": attribute_value, + } + + response = do_request(s, request) + + if 'code' not in response or response['code'] != 200: + raise Exception(f'Failed to write {attribute_name}: {response}') + + +def main(): + """ Main program entry point. """ + with socket.socket() as s: + + # Connect to the stabilizer. + s.connect((HOST, PORT)) + + # A sample configuration for an output channel. + channel_config = { + 'attenuation': 31.5, + 'parameters': { + 'phase_offset': 0.5, + 'frequency': 100.0e6, + 'amplitude': 0.2, + 'enabled': True, + } + } + + # Configure OUT0 and read it back. + write_attribute(s, "pounder/out0", channel_config) + print('Pounder OUT0: ', read_attribute(s, "pounder/out0")) + + print('Pounder IN1: ', read_attribute(s, "pounder/in1")) + print('Pounder OUT1: ', read_attribute(s, "pounder/out1")) + +if __name__ == '__main__': + main() diff --git a/src/board.rs b/src/board.rs deleted file mode 100644 index 572b1c2..0000000 --- a/src/board.rs +++ /dev/null @@ -1,641 +0,0 @@ -use super::eth; -use super::i2c; -use super::pac; - -fn pwr_setup(pwr: &pac::PWR) { - // go to VOS1 voltage scale for high perf - pwr.cr3 - .write(|w| w.scuen().set_bit().ldoen().set_bit().bypass().clear_bit()); - while pwr.csr1.read().actvosrdy().bit_is_clear() {} - pwr.d3cr.write(|w| unsafe { w.vos().bits(0b11) }); // vos1 - while pwr.d3cr.read().vosrdy().bit_is_clear() {} -} - -fn rcc_reset(rcc: &pac::RCC) { - // Reset all peripherals - rcc.ahb1rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.ahb1rstr.write(|w| unsafe { w.bits(0) }); - rcc.apb1lrstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.apb1lrstr.write(|w| unsafe { w.bits(0) }); - rcc.apb1hrstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.apb1hrstr.write(|w| unsafe { w.bits(0) }); - - rcc.ahb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.ahb2rstr.write(|w| unsafe { w.bits(0) }); - rcc.apb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.apb2rstr.write(|w| unsafe { w.bits(0) }); - - // do not reset the cpu - rcc.ahb3rstr.write(|w| unsafe { w.bits(0x7FFF_FFFF) }); - rcc.ahb3rstr.write(|w| unsafe { w.bits(0) }); - rcc.apb3rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.apb3rstr.write(|w| unsafe { w.bits(0) }); - - rcc.ahb4rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.ahb4rstr.write(|w| unsafe { w.bits(0) }); - rcc.apb4rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - rcc.apb4rstr.write(|w| unsafe { w.bits(0) }); -} - -fn rcc_pll_setup(rcc: &pac::RCC, flash: &pac::FLASH) { - // Switch to HSI to mess with HSE - rcc.cr.modify(|_, w| w.hsion().on()); - while rcc.cr.read().hsirdy().is_not_ready() {} - rcc.cfgr.modify(|_, w| w.sw().hsi()); - while !rcc.cfgr.read().sws().is_hsi() {} - rcc.cr.write(|w| w.hsion().on()); - rcc.cfgr.reset(); - - // Ensure HSE is on and stable - rcc.cr.modify(|_, w| w.hseon().on().hsebyp().not_bypassed()); - while !rcc.cr.read().hserdy().is_ready() {} - - rcc.pllckselr.modify( - |_, w| { - w.pllsrc() - .hse() - .divm1() - .bits(1) // ref prescaler - .divm2() - .bits(1) - }, // ref prescaler - ); - // Configure PLL1: 8MHz /1 *100 /2 = 400 MHz - rcc.pllcfgr.modify(|_, w| { - w.pll1vcosel() - .wide_vco() // 192-836 MHz VCO - .pll1rge() - .range8() // 8-16 MHz PFD - .pll1fracen() - .reset() - .divp1en() - .enabled() - .pll2vcosel() - .medium_vco() // 150-420 MHz VCO - .pll2rge() - .range8() // 8-16 MHz PFD - .pll2fracen() - .reset() - .divp2en() - .enabled() - .divq2en() - .enabled() - }); - rcc.pll1divr.write(|w| unsafe { - w.divn1() - .bits(100 - 1) // feebdack divider - .divp1() - .div2() // p output divider - }); - rcc.cr.modify(|_, w| w.pll1on().on()); - while !rcc.cr.read().pll1rdy().is_ready() {} - - // Configure PLL2: 8MHz /1 *25 / 2 = 100 MHz - rcc.pll2divr.write(|w| unsafe { - w.divn2() - .bits(25 - 1) // feebdack divider - .divp2() - .bits(2 - 1) // p output divider - .divq2() - .bits(2 - 1) // q output divider - }); - rcc.cr.modify(|_, w| w.pll2on().on()); - while !rcc.cr.read().pll2rdy().is_ready() {} - - // hclk 200 MHz, pclk 100 MHz - rcc.d1cfgr.write( - |w| { - w.d1cpre() - .div1() // sys_ck not divided - .hpre() - .div2() // rcc_hclk3 = sys_d1cpre_ck / 2 - .d1ppre() - .div2() - }, // rcc_pclk3 = rcc_hclk3 / 2 - ); - rcc.d2cfgr.write( - |w| { - w.d2ppre1() - .div2() // rcc_pclk1 = rcc_hclk3 / 2 - .d2ppre2() - .div2() - }, // rcc_pclk2 = rcc_hclk3 / 2 - ); - rcc.d3cfgr.write( - |w| w.d3ppre().div2(), // rcc_pclk4 = rcc_hclk3 / 2 - ); - - // 2 wait states, 0b10 programming delay - // 185-210 MHz - flash - .acr - .write(|w| unsafe { w.wrhighfreq().bits(2).latency().bits(2) }); - while flash.acr.read().latency().bits() != 2 {} - - // CSI for I/O compensationc ell - rcc.cr.modify(|_, w| w.csion().on()); - while !rcc.cr.read().csirdy().is_ready() {} - - // Set system clock to pll1_p - rcc.cfgr.modify(|_, w| w.sw().pll1()); - while !rcc.cfgr.read().sws().is_pll1() {} - - rcc.d1ccipr.write(|w| w.ckpersel().hse()); - rcc.d2ccip1r - .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q()); - rcc.d3ccipr.modify(|_, w| w.spi6sel().pll2_q()); -} - -fn io_compensation_setup(syscfg: &pac::SYSCFG) { - syscfg - .cccsr - .modify(|_, w| w.en().set_bit().cs().clear_bit().hslv().clear_bit()); - while syscfg.cccsr.read().ready().bit_is_clear() {} -} - -fn gpio_setup( - gpioa: &pac::GPIOA, - gpiob: &pac::GPIOB, - gpiod: &pac::GPIOD, - gpioe: &pac::GPIOE, - gpiof: &pac::GPIOF, - gpiog: &pac::GPIOG, -) { - // FP_LED0 - gpiod.otyper.modify(|_, w| w.ot5().push_pull()); - gpiod.moder.modify(|_, w| w.moder5().output()); - gpiod.odr.modify(|_, w| w.odr5().low()); - - // FP_LED1 - gpiod.otyper.modify(|_, w| w.ot6().push_pull()); - gpiod.moder.modify(|_, w| w.moder6().output()); - gpiod.odr.modify(|_, w| w.odr6().low()); - - // LED_FP2 - gpiog.otyper.modify(|_, w| w.ot4().push_pull()); - gpiog.moder.modify(|_, w| w.moder4().output()); - gpiog.odr.modify(|_, w| w.odr4().low()); - - // LED_FP3 - gpiod.otyper.modify(|_, w| w.ot12().push_pull()); - gpiod.moder.modify(|_, w| w.moder12().output()); - gpiod.odr.modify(|_, w| w.odr12().low()); - - // AFE0_A0,1: PG2,PG3 - gpiog - .otyper - .modify(|_, w| w.ot2().push_pull().ot3().push_pull()); - gpiog - .moder - .modify(|_, w| w.moder2().output().moder3().output()); - gpiog.odr.modify(|_, w| w.odr2().low().odr3().low()); - - // ADC0 - // SCK: PG11 - gpiog.moder.modify(|_, w| w.moder11().alternate()); - gpiog.otyper.modify(|_, w| w.ot11().push_pull()); - gpiog.ospeedr.modify(|_, w| w.ospeedr11().very_high_speed()); - gpiog.afrh.modify(|_, w| w.afr11().af5()); - // MOSI: PD7 - // MISO: PA6 - gpioa.moder.modify(|_, w| w.moder6().alternate()); - gpioa.afrl.modify(|_, w| w.afr6().af5()); - // NSS: PG10 - gpiog.moder.modify(|_, w| w.moder10().alternate()); - gpiog.otyper.modify(|_, w| w.ot10().push_pull()); - gpiog.ospeedr.modify(|_, w| w.ospeedr10().very_high_speed()); - gpiog.afrh.modify(|_, w| w.afr10().af5()); - - // DAC0 - // SCK: PB10 - gpiob.moder.modify(|_, w| w.moder10().alternate()); - gpiob.otyper.modify(|_, w| w.ot10().push_pull()); - gpiob.ospeedr.modify(|_, w| w.ospeedr10().very_high_speed()); - gpiob.afrh.modify(|_, w| w.afr10().af5()); - // MOSI: PB15 - gpiob.moder.modify(|_, w| w.moder15().alternate()); - gpiob.otyper.modify(|_, w| w.ot15().push_pull()); - gpiob.ospeedr.modify(|_, w| w.ospeedr15().very_high_speed()); - gpiob.afrh.modify(|_, w| w.afr15().af5()); - // MISO: PB14 - // NSS: PB9 - gpiob.moder.modify(|_, w| w.moder9().alternate()); - gpiob.otyper.modify(|_, w| w.ot9().push_pull()); - gpiob.ospeedr.modify(|_, w| w.ospeedr9().very_high_speed()); - gpiob.afrh.modify(|_, w| w.afr9().af5()); - - // DAC0_LDAC: PE11 - gpioe.moder.modify(|_, w| w.moder11().output()); - gpioe.otyper.modify(|_, w| w.ot11().push_pull()); - gpioe.odr.modify(|_, w| w.odr11().low()); - - // DAC_CLR: PE12 - gpioe.moder.modify(|_, w| w.moder12().output()); - gpioe.otyper.modify(|_, w| w.ot12().push_pull()); - gpioe.odr.modify(|_, w| w.odr12().high()); - - // AFE1_A0,1: PD14,PD15 - gpiod - .otyper - .modify(|_, w| w.ot14().push_pull().ot15().push_pull()); - gpiod - .moder - .modify(|_, w| w.moder14().output().moder15().output()); - gpiod.odr.modify(|_, w| w.odr14().low().odr15().low()); - - // I2C2: SDA,SCL: PF0,PF1 - gpiof - .moder - .modify(|_, w| w.moder0().alternate().moder1().alternate()); - gpiof.afrl.modify(|_, w| w.afr0().af4().afr1().af4()); - gpiof - .otyper - .modify(|_, w| w.ot0().open_drain().ot1().open_drain()); - - // ADC1 - // SCK: PF6 - gpiof.moder.modify(|_, w| w.moder7().alternate()); - gpiof.otyper.modify(|_, w| w.ot7().push_pull()); - gpiof.ospeedr.modify(|_, w| w.ospeedr7().very_high_speed()); - gpiof.afrl.modify(|_, w| w.afr7().af5()); - // MOSI: PF9 - // MISO: PF7 - gpiof.moder.modify(|_, w| w.moder8().alternate()); - gpiof.afrh.modify(|_, w| w.afr8().af5()); - // NSS: PF8 - gpiof.moder.modify(|_, w| w.moder6().alternate()); - gpiof.otyper.modify(|_, w| w.ot6().push_pull()); - gpiof.ospeedr.modify(|_, w| w.ospeedr6().very_high_speed()); - gpiof.afrl.modify(|_, w| w.afr6().af5()); - - // DAC1 - // SCK: PE2 - gpioe.moder.modify(|_, w| w.moder2().alternate()); - gpioe.otyper.modify(|_, w| w.ot2().push_pull()); - gpioe.ospeedr.modify(|_, w| w.ospeedr2().very_high_speed()); - gpioe.afrl.modify(|_, w| w.afr2().af5()); - // MOSI: PE6 - gpioe.moder.modify(|_, w| w.moder6().alternate()); - gpioe.otyper.modify(|_, w| w.ot6().push_pull()); - gpioe.ospeedr.modify(|_, w| w.ospeedr6().very_high_speed()); - gpioe.afrl.modify(|_, w| w.afr6().af5()); - // MISO: PE5 - // NSS: PE4 - gpioe.moder.modify(|_, w| w.moder4().alternate()); - gpioe.otyper.modify(|_, w| w.ot4().push_pull()); - gpioe.ospeedr.modify(|_, w| w.ospeedr4().very_high_speed()); - gpioe.afrl.modify(|_, w| w.afr4().af5()); - - // DAC1_LDAC: PE15 - gpioe.moder.modify(|_, w| w.moder15().output()); - gpioe.otyper.modify(|_, w| w.ot15().push_pull()); - gpioe.odr.modify(|_, w| w.odr15().low()); -} - -// ADC0 -fn spi1_setup(spi1: &pac::SPI1) { - spi1.cfg1 - .modify(|_, w| w.mbr().div4().dsize().bits(16 - 1).fthlv().one_frame()); - spi1.cfg2.modify(|_, w| { - w.afcntr() - .controlled() - .ssom() - .not_asserted() - .ssoe() - .enabled() - .ssiop() - .active_low() - .ssm() - .disabled() - .cpol() - .idle_high() - .cpha() - .second_edge() - .lsbfrst() - .msbfirst() - .master() - .master() - .sp() - .motorola() - .comm() - .receiver() - .ioswp() - .disabled() - .midi() - .bits(0) - .mssi() - .bits(6) - }); - spi1.cr2.modify(|_, w| w.tsize().bits(1)); - spi1.cr1.write(|w| w.spe().enabled()); -} - -// ADC1 -fn spi5_setup(spi5: &pac::SPI5) { - spi5.cfg1 - .modify(|_, w| w.mbr().div4().dsize().bits(16 - 1).fthlv().one_frame()); - spi5.cfg2.modify(|_, w| { - w.afcntr() - .controlled() - .ssom() - .not_asserted() - .ssoe() - .enabled() - .ssiop() - .active_low() - .ssm() - .disabled() - .cpol() - .idle_high() - .cpha() - .second_edge() - .lsbfrst() - .msbfirst() - .master() - .master() - .sp() - .motorola() - .comm() - .receiver() - .ioswp() - .disabled() - .midi() - .bits(0) - .mssi() - .bits(6) - }); - spi5.cr2.modify(|_, w| w.tsize().bits(1)); - spi5.cr1.write(|w| w.spe().enabled()); -} - -// DAC0 -fn spi2_setup(spi2: &pac::SPI2) { - spi2.cfg1 - .modify(|_, w| w.mbr().div2().dsize().bits(16 - 1).fthlv().one_frame()); - spi2.cfg2.modify(|_, w| { - w.afcntr() - .controlled() - .ssom() - .not_asserted() - .ssoe() - .enabled() - .ssiop() - .active_low() - .ssm() - .disabled() - .cpol() - .idle_low() - .cpha() - .first_edge() - .lsbfrst() - .msbfirst() - .master() - .master() - .sp() - .motorola() - .comm() - .transmitter() - .ioswp() - .disabled() - .midi() - .bits(0) - .mssi() - .bits(0) - }); - spi2.cr2.modify(|_, w| w.tsize().bits(0)); - spi2.cr1.write(|w| w.spe().enabled()); - spi2.cr1.modify(|_, w| w.cstart().started()); -} - -// DAC1 -fn spi4_setup(spi4: &pac::SPI4) { - spi4.cfg1 - .modify(|_, w| w.mbr().div2().dsize().bits(16 - 1).fthlv().one_frame()); - spi4.cfg2.modify(|_, w| { - w.afcntr() - .controlled() - .ssom() - .not_asserted() - .ssoe() - .enabled() - .ssiop() - .active_low() - .ssm() - .disabled() - .cpol() - .idle_low() - .cpha() - .first_edge() - .lsbfrst() - .msbfirst() - .master() - .master() - .sp() - .motorola() - .comm() - .transmitter() - .ioswp() - .disabled() - .midi() - .bits(0) - .mssi() - .bits(0) - }); - spi4.cr2.modify(|_, w| w.tsize().bits(0)); - spi4.cr1.write(|w| w.spe().enabled()); - spi4.cr1.modify(|_, w| w.cstart().started()); -} - -fn tim2_setup(tim2: &pac::TIM2) { - tim2.psc.write(|w| w.psc().bits(200 - 1)); // from 200 MHz - tim2.arr.write(|w| unsafe { w.bits(2 - 1) }); // 2 µs - tim2.dier.write(|w| w.ude().set_bit()); - tim2.egr.write(|w| w.ug().set_bit()); - tim2.cr1.modify(|_, w| w.dir().clear_bit()); // up -} - -fn dma1_setup( - dma1: &pac::DMA1, - dmamux1: &pac::DMAMUX1, - ma: usize, - pa0: usize, - pa1: usize, -) { - dma1.st[0].cr.modify(|_, w| w.en().clear_bit()); - while dma1.st[0].cr.read().en().bit_is_set() {} - - dma1.st[0].par.write(|w| unsafe { w.bits(pa0 as u32) }); - dma1.st[0].m0ar.write(|w| unsafe { w.bits(ma as u32) }); - dma1.st[0].ndtr.write(|w| w.ndt().bits(1)); - dmamux1.ccr[0].modify(|_, w| w.dmareq_id().tim2_up()); - dma1.st[0].cr.modify(|_, w| { - w.pl() - .medium() - .circ() - .enabled() - .msize() - .bits32() - .minc() - .fixed() - .mburst() - .single() - .psize() - .bits32() - .pinc() - .fixed() - .pburst() - .single() - .dbm() - .disabled() - .dir() - .memory_to_peripheral() - .pfctrl() - .dma() - }); - dma1.st[0].fcr.modify(|_, w| w.dmdis().clear_bit()); - dma1.st[0].cr.modify(|_, w| w.en().set_bit()); - - dma1.st[1].cr.modify(|_, w| w.en().clear_bit()); - while dma1.st[1].cr.read().en().bit_is_set() {} - - dma1.st[1].par.write(|w| unsafe { w.bits(pa1 as u32) }); - dma1.st[1].m0ar.write(|w| unsafe { w.bits(ma as u32) }); - dma1.st[1].ndtr.write(|w| w.ndt().bits(1)); - dmamux1.ccr[1].modify(|_, w| w.dmareq_id().tim2_up()); - dma1.st[1].cr.modify(|_, w| { - w.pl() - .medium() - .circ() - .enabled() - .msize() - .bits32() - .minc() - .fixed() - .mburst() - .single() - .psize() - .bits32() - .pinc() - .fixed() - .pburst() - .single() - .dbm() - .disabled() - .dir() - .memory_to_peripheral() - .pfctrl() - .dma() - }); - dma1.st[1].fcr.modify(|_, w| w.dmdis().clear_bit()); - dma1.st[1].cr.modify(|_, w| w.en().set_bit()); -} - -#[link_section = ".sram1.datspi"] -static mut DAT: u32 = 0x201; // EN | CSTART - -pub fn init() { - let mut cp = unsafe { cortex_m::Peripherals::steal() }; - let dp = unsafe { pac::Peripherals::steal() }; - - let rcc = dp.RCC; - rcc_reset(&rcc); - pwr_setup(&dp.PWR); - rcc_pll_setup(&rcc, &dp.FLASH); - rcc.apb4enr.modify(|_, w| w.syscfgen().set_bit()); - io_compensation_setup(&dp.SYSCFG); - - cp.SCB.enable_icache(); - // TODO: ETH DMA coherence issues - // cp.SCB.enable_dcache(&mut cp.CPUID); - cp.DWT.enable_cycle_counter(); // japaric/cortex-m-rtfm#184 - - rcc.ahb4enr.modify(|_, w| { - w.gpioaen() - .set_bit() - .gpioben() - .set_bit() - .gpiocen() - .set_bit() - .gpioden() - .set_bit() - .gpioeen() - .set_bit() - .gpiofen() - .set_bit() - .gpiogen() - .set_bit() - }); - gpio_setup( - &dp.GPIOA, &dp.GPIOB, &dp.GPIOD, &dp.GPIOE, &dp.GPIOF, &dp.GPIOG, - ); - - rcc.apb1lenr.modify(|_, w| w.spi2en().set_bit()); - let spi2 = dp.SPI2; - spi2_setup(&spi2); - - rcc.apb2enr.modify(|_, w| w.spi4en().set_bit()); - let spi4 = dp.SPI4; - spi4_setup(&spi4); - - rcc.apb2enr.modify(|_, w| w.spi1en().set_bit()); - let spi1 = dp.SPI1; - spi1_setup(&spi1); - spi1.ier.write(|w| w.eotie().set_bit()); - - rcc.apb2enr.modify(|_, w| w.spi5en().set_bit()); - let spi5 = dp.SPI5; - spi5_setup(&spi5); - // spi5.ier.write(|w| w.eotie().set_bit()); - - rcc.ahb2enr.modify(|_, w| { - w.sram1en() - .set_bit() - .sram2en() - .set_bit() - .sram3en() - .set_bit() - }); - rcc.ahb1enr.modify(|_, w| w.dma1en().set_bit()); - // init SRAM1 rodata can't load with sram1 disabled - unsafe { DAT = 0x201 }; // EN | CSTART - cortex_m::asm::dsb(); - let dat_addr = unsafe { &DAT as *const _ } as usize; - cp.SCB.clean_dcache_by_address(dat_addr, 4); - - dma1_setup( - &dp.DMA1, - &dp.DMAMUX1, - dat_addr, - &spi1.cr1 as *const _ as usize, - &spi5.cr1 as *const _ as usize, - ); - - rcc.apb1lenr.modify(|_, w| w.tim2en().set_bit()); - - // work around the SPI stall erratum - let dbgmcu = dp.DBGMCU; - dbgmcu.apb1lfz1.modify(|_, w| w.tim2().set_bit()); - - tim2_setup(&dp.TIM2); - - rcc.apb1lenr.modify(|_, w| w.i2c2en().set_bit()); - i2c::setup(&dp.I2C2); - - rcc.apb4enr.modify(|_, w| w.syscfgen().set_bit()); - rcc.ahb1enr.modify(|_, w| { - w.eth1macen() - .set_bit() - .eth1txen() - .set_bit() - .eth1rxen() - .set_bit() - }); - dp.SYSCFG - .pmcr - .modify(|_, w| unsafe { w.epis().bits(0b100) }); // RMII - eth::setup_pins(&dp.GPIOA, &dp.GPIOB, &dp.GPIOC, &dp.GPIOG); - - // enable TIM2 this must be late to be able to handle the first ADC SPI - // interrupt in time - dp.TIM2.cr1.modify(|_, w| w.cen().set_bit()); -} diff --git a/src/eeprom.rs b/src/eeprom.rs deleted file mode 100644 index fe30680..0000000 --- a/src/eeprom.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::i2c; -use stm32h7::stm32h743 as pac; - -const I2C_ADDR: u8 = 0xa0; - -pub fn read_eui48(i2c: &pac::I2C2) -> Result<[u8; 6], i2c::Error> { - let mut buffer = [0u8; 6]; - i2c::write_read(i2c, I2C_ADDR, &[0xFAu8], &mut buffer)?; - Ok(buffer) -} diff --git a/src/eth.rs b/src/eth.rs deleted file mode 100644 index 9cd1c9c..0000000 --- a/src/eth.rs +++ /dev/null @@ -1,649 +0,0 @@ -use core::{cmp, slice}; -use smoltcp::phy; -use smoltcp::time::Instant; -use smoltcp::wire::EthernetAddress; -use smoltcp::Result; -use stm32h7::stm32h743 as pac; - -#[allow(dead_code)] -mod phy_consts { - pub const PHY_REG_BCR: u8 = 0x00; - pub const PHY_REG_BSR: u8 = 0x01; - pub const PHY_REG_ID1: u8 = 0x02; - pub const PHY_REG_ID2: u8 = 0x03; - pub const PHY_REG_ANTX: u8 = 0x04; - pub const PHY_REG_ANRX: u8 = 0x05; - pub const PHY_REG_ANEXP: u8 = 0x06; - pub const PHY_REG_ANNPTX: u8 = 0x07; - pub const PHY_REG_ANNPRX: u8 = 0x08; - pub const PHY_REG_SSR: u8 = 0x1F; // Special Status Register - pub const PHY_REG_CTL: u8 = 0x0D; // Ethernet PHY Register Control - pub const PHY_REG_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data - - pub const PHY_REG_WUCSR: u16 = 0x8010; - - pub const PHY_REG_BCR_COLTEST: u16 = 1 << 7; - pub const PHY_REG_BCR_FD: u16 = 1 << 8; - pub const PHY_REG_BCR_ANRST: u16 = 1 << 9; - pub const PHY_REG_BCR_ISOLATE: u16 = 1 << 10; - pub const PHY_REG_BCR_POWERDN: u16 = 1 << 11; - pub const PHY_REG_BCR_AN: u16 = 1 << 12; - pub const PHY_REG_BCR_100M: u16 = 1 << 13; - pub const PHY_REG_BCR_LOOPBACK: u16 = 1 << 14; - pub const PHY_REG_BCR_RESET: u16 = 1 << 15; - - pub const PHY_REG_BSR_JABBER: u16 = 1 << 1; - pub const PHY_REG_BSR_UP: u16 = 1 << 2; - pub const PHY_REG_BSR_FAULT: u16 = 1 << 4; - pub const PHY_REG_BSR_ANDONE: u16 = 1 << 5; - - pub const PHY_REG_SSR_ANDONE: u16 = 1 << 12; - pub const PHY_REG_SSR_SPEED: u16 = 0b111 << 2; - pub const PHY_REG_SSR_10BASE_HD: u16 = 0b001 << 2; - pub const PHY_REG_SSR_10BASE_FD: u16 = 0b101 << 2; - pub const PHY_REG_SSR_100BASE_HD: u16 = 0b010 << 2; - pub const PHY_REG_SSR_100BASE_FD: u16 = 0b110 << 2; -} -use self::phy_consts::*; - -const EMAC_DES3_OWN: u32 = 0x8000_0000; -const EMAC_DES3_CTXT: u32 = 0x4000_0000; -const EMAC_DES3_FD: u32 = 0x2000_0000; -const EMAC_DES3_LD: u32 = 0x1000_0000; -const EMAC_DES3_ES: u32 = 0x0000_8000; -const EMAC_TDES2_IOC: u32 = 0x8000_0000; -const EMAC_RDES3_IOC: u32 = 0x4000_0000; -const EMAC_RDES3_PL: u32 = 0x0000_7FFF; -const EMAC_RDES3_BUF1V: u32 = 0x0100_0000; -const EMAC_TDES2_B1L: u32 = 0x0000_3FFF; -const EMAC_DES0_BUF1AP: u32 = 0xFFFF_FFFF; - -// 6 DMAC, 6 SMAC, 4 q tag, 2 ethernet type II, 1500 ip MTU, 4 CRC, 2 padding -const ETH_BUFFER_SIZE: usize = 1524; -const ETH_DESC_U32_SIZE: usize = 4; -const ETH_TX_BUFFER_COUNT: usize = 4; -const ETH_RX_BUFFER_COUNT: usize = 4; - -#[allow(dead_code)] -mod cr_consts { - /* For HCLK 60-100 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_42: u8 = 0; - /* For HCLK 100-150 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_62: u8 = 1; - /* For HCLK 20-35 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_16: u8 = 2; - /* For HCLK 35-60 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_26: u8 = 3; - /* For HCLK 150-250 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_102: u8 = 4; - /* For HCLK 250-300 MHz */ - pub const ETH_MACMIIAR_CR_HCLK_DIV_124: u8 = 5; -} -use self::cr_consts::*; - -// set clock range in MAC MII address register -// 200 MHz AHB clock = eth_hclk -const CLOCK_RANGE: u8 = ETH_MACMIIAR_CR_HCLK_DIV_102; - -pub fn setup_pins( - gpioa: &pac::GPIOA, - gpiob: &pac::GPIOB, - gpioc: &pac::GPIOC, - gpiog: &pac::GPIOG, -) { - // PA1 RMII_REF_CLK - gpioa.moder.modify(|_, w| w.moder1().alternate()); - gpioa.afrl.modify(|_, w| w.afr1().af11()); - gpioa.ospeedr.modify(|_, w| w.ospeedr1().very_high_speed()); - // PA2 RMII_MDIO - gpioa.moder.modify(|_, w| w.moder2().alternate()); - gpioa.afrl.modify(|_, w| w.afr2().af11()); - gpioa.ospeedr.modify(|_, w| w.ospeedr2().very_high_speed()); - // PC1 RMII_MDC - gpioc.moder.modify(|_, w| w.moder1().alternate()); - gpioc.afrl.modify(|_, w| w.afr1().af11()); - gpioc.ospeedr.modify(|_, w| w.ospeedr1().very_high_speed()); - // PA7 RMII_CRS_DV - gpioa.moder.modify(|_, w| w.moder7().alternate()); - gpioa.afrl.modify(|_, w| w.afr7().af11()); - gpioa.ospeedr.modify(|_, w| w.ospeedr7().very_high_speed()); - // PC4 RMII_RXD0 - gpioc.moder.modify(|_, w| w.moder4().alternate()); - gpioc.afrl.modify(|_, w| w.afr4().af11()); - gpioc.ospeedr.modify(|_, w| w.ospeedr4().very_high_speed()); - // PC5 RMII_RXD1 - gpioc.moder.modify(|_, w| w.moder5().alternate()); - gpioc.afrl.modify(|_, w| w.afr5().af11()); - gpioc.ospeedr.modify(|_, w| w.ospeedr5().very_high_speed()); - // PB11 RMII_TX_EN - gpiob.moder.modify(|_, w| w.moder11().alternate()); - gpiob.afrh.modify(|_, w| w.afr11().af11()); - gpiob.ospeedr.modify(|_, w| w.ospeedr11().very_high_speed()); - // PB12 RXII_TXD0 - gpiob.moder.modify(|_, w| w.moder12().alternate()); - gpiob.afrh.modify(|_, w| w.afr12().af11()); - gpiob.ospeedr.modify(|_, w| w.ospeedr12().very_high_speed()); - // PG14 RMII TXD1 - gpiog.moder.modify(|_, w| w.moder14().alternate()); - gpiog.afrh.modify(|_, w| w.afr14().af11()); - gpiog.ospeedr.modify(|_, w| w.ospeedr14().very_high_speed()); -} - -const PHY_ADDR: u8 = 0; - -fn phy_read(reg_addr: u8, mac: &pac::ETHERNET_MAC) -> u16 { - while mac.macmdioar.read().mb().bit_is_set() {} - mac.macmdioar.modify(|_, w| unsafe { - w.pa() - .bits(PHY_ADDR) - .rda() - .bits(reg_addr) - .goc() - .bits(0b11) // read - .cr() - .bits(CLOCK_RANGE) - .mb() - .set_bit() - }); - while mac.macmdioar.read().mb().bit_is_set() {} - mac.macmdiodr.read().md().bits() -} - -fn phy_write(reg_addr: u8, reg_data: u16, mac: &pac::ETHERNET_MAC) { - while mac.macmdioar.read().mb().bit_is_set() {} - mac.macmdiodr.write(|w| unsafe { w.md().bits(reg_data) }); - mac.macmdioar.modify(|_, w| unsafe { - w.pa() - .bits(PHY_ADDR) - .rda() - .bits(reg_addr) - .goc() - .bits(0b01) // write - .cr() - .bits(CLOCK_RANGE) - .mb() - .set_bit() - }); - while mac.macmdioar.read().mb().bit_is_set() {} -} - -// Writes a value to an extended PHY register in MMD address space -fn phy_write_ext(reg_addr: u16, reg_data: u16, mac: &pac::ETHERNET_MAC) { - phy_write(PHY_REG_CTL, 0x0003, mac); // set address - phy_write(PHY_REG_ADDAR, reg_addr, mac); - phy_write(PHY_REG_CTL, 0x4003, mac); // set data - phy_write(PHY_REG_ADDAR, reg_data, mac); -} - -#[repr(align(4))] -struct RxRing { - desc_buf: [[u32; ETH_DESC_U32_SIZE]; ETH_RX_BUFFER_COUNT], - pkt_buf: [[u8; ETH_BUFFER_SIZE]; ETH_RX_BUFFER_COUNT], - cur_desc: usize, -} - -impl RxRing { - const fn new() -> Self { - Self { - desc_buf: [[0; ETH_DESC_U32_SIZE]; ETH_RX_BUFFER_COUNT], - pkt_buf: [[0; ETH_BUFFER_SIZE]; ETH_RX_BUFFER_COUNT], - cur_desc: 0, - } - } - - unsafe fn init(&mut self, dma: &pac::ETHERNET_DMA) { - assert_eq!(self.desc_buf[0].len() % 4, 0); - assert_eq!(self.pkt_buf[0].len() % 4, 0); - - for i in 0..self.desc_buf.len() { - for j in 0..self.desc_buf[0].len() { - self.desc_buf[i][j] = 0; - } - for j in 0..self.pkt_buf[0].len() { - self.pkt_buf[i][j] = 0; - } - } - - let addr = &self.desc_buf as *const _ as u32; - assert_eq!(addr & 0x3, 0); - dma.dmacrx_dlar.write(|w| w.bits(addr)); - dma.dmacrx_rlr - .write(|w| w.rdrl().bits(self.desc_buf.len() as u16 - 1)); - - self.cur_desc = 0; - for _ in 0..self.desc_buf.len() { - self.buf_release() - } - } - - fn next_desc(&self) -> usize { - (self.cur_desc + 1) % self.desc_buf.len() - } - - // not owned by DMA - fn buf_owned(&self) -> bool { - self.desc_buf[self.cur_desc][3] & EMAC_DES3_OWN == 0 - } - - fn buf_valid(&self) -> bool { - self.desc_buf[self.cur_desc][3] - & (EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_ES | EMAC_DES3_CTXT) - == (EMAC_DES3_FD | EMAC_DES3_LD) - } - - unsafe fn buf_as_slice_mut<'a>(&self) -> &'a mut [u8] { - let len = (self.desc_buf[self.cur_desc][3] & EMAC_RDES3_PL) as usize; - let len = cmp::min(len, ETH_BUFFER_SIZE); - let addr = &self.pkt_buf[self.cur_desc] as *const _ as *mut u8; - slice::from_raw_parts_mut(addr, len) - } - - fn buf_release(&mut self) { - let addr = &self.pkt_buf[self.cur_desc] as *const _; - self.desc_buf[self.cur_desc][0] = addr as u32 & EMAC_DES0_BUF1AP; - self.desc_buf[self.cur_desc][3] = - EMAC_RDES3_BUF1V | EMAC_RDES3_IOC | EMAC_DES3_OWN; - - let addr = &self.desc_buf[self.cur_desc] as *const _ as u32; - assert_eq!(addr & 0x3, 0); - - let dma = unsafe { pac::Peripherals::steal().ETHERNET_DMA }; - - // Ensure changes to the descriptor (in particular, the OWN flag) are - // committed before DMA engine sees tail pointer store. - cortex_m::asm::dsb(); - - dma.dmacrx_dtpr.write(|w| unsafe { w.bits(addr) }); - - self.cur_desc = self.next_desc(); - } -} - -#[repr(align(4))] -struct TxRing { - desc_buf: [[u32; ETH_DESC_U32_SIZE]; ETH_TX_BUFFER_COUNT], - pkt_buf: [[u8; ETH_BUFFER_SIZE]; ETH_TX_BUFFER_COUNT], - cur_desc: usize, -} - -impl TxRing { - const fn new() -> Self { - Self { - desc_buf: [[0; ETH_DESC_U32_SIZE]; ETH_TX_BUFFER_COUNT], - pkt_buf: [[0; ETH_BUFFER_SIZE]; ETH_TX_BUFFER_COUNT], - cur_desc: 0, - } - } - - unsafe fn init(&mut self, dma: &pac::ETHERNET_DMA) { - assert_eq!(self.desc_buf[0].len() % 4, 0); - assert_eq!(self.pkt_buf[0].len() % 4, 0); - - for i in 0..self.desc_buf.len() { - for j in 0..self.desc_buf[0].len() { - self.desc_buf[i][j] = 0; - } - for j in 0..self.pkt_buf[0].len() { - self.pkt_buf[i][j] = 0; - } - } - self.cur_desc = 0; - - let addr = &self.desc_buf as *const _ as u32; - assert_eq!(addr & 0x3, 0); - dma.dmactx_dlar.write(|w| w.bits(addr)); - dma.dmactx_rlr - .write(|w| w.tdrl().bits(self.desc_buf.len() as u16 - 1)); - let addr = &self.desc_buf[0] as *const _ as u32; - assert_eq!(addr & 0x3, 0); - dma.dmactx_dtpr.write(|w| w.bits(addr)); - } - - fn next_desc(&self) -> usize { - (self.cur_desc + 1) % self.desc_buf.len() - } - - // not owned by DMA - fn buf_owned(&self) -> bool { - self.desc_buf[self.cur_desc][3] & EMAC_DES3_OWN == 0 - } - - unsafe fn buf_as_slice_mut<'a>(&mut self, len: usize) -> &'a mut [u8] { - let len = cmp::min(len, ETH_BUFFER_SIZE); - self.desc_buf[self.cur_desc][2] = - EMAC_TDES2_IOC | (len as u32 & EMAC_TDES2_B1L); - let addr = &self.pkt_buf[self.cur_desc] as *const _ as *mut u8; - self.desc_buf[self.cur_desc][0] = addr as u32 & EMAC_DES0_BUF1AP; - slice::from_raw_parts_mut(addr, len) - } - - fn buf_release(&mut self) { - self.desc_buf[self.cur_desc][3] = - EMAC_DES3_OWN | EMAC_DES3_FD | EMAC_DES3_LD; - self.cur_desc = self.next_desc(); - - let addr = &self.desc_buf[self.cur_desc] as *const _ as u32; - assert_eq!(addr & 0x3, 0); - - let dma = unsafe { pac::Peripherals::steal().ETHERNET_DMA }; - - // Ensure packet contents as well as changes to the descriptor have been - // committed before DMA engine sees the tail pointer store. - cortex_m::asm::dsb(); - - dma.dmactx_dtpr.write(|w| unsafe { w.bits(addr) }); - } -} - -pub struct Device { - rx: RxRing, - tx: TxRing, -} - -impl Device { - pub const fn new() -> Self { - Self { - rx: RxRing::new(), - tx: TxRing::new(), - } - } - - // Initialize the ethernet peripherals - // - // # Safety - // - // This iis transitively unsafe since it sets potentially - // unsafe register values. Might ultimately be safe if the values - // are correct. - // - // After `init` is called, `Device` shall not be moved. - pub unsafe fn init( - &mut self, - mac: EthernetAddress, - eth_mac: &pac::ETHERNET_MAC, - eth_dma: &pac::ETHERNET_DMA, - eth_mtl: &pac::ETHERNET_MTL, - ) { - eth_dma.dmamr.modify(|_, w| w.swr().set_bit()); - while eth_dma.dmamr.read().swr().bit_is_set() {} - - // 200 MHz - eth_mac - .mac1ustcr - .modify(|_, w| w.tic_1us_cntr().bits(200 - 1)); - - // Configuration Register - eth_mac.maccr.modify(|_, w| { - w.arpen() - .clear_bit() - .ipc() - .set_bit() - .ipg() - .bits(0b000) // 96 bit - .ecrsfd() - .clear_bit() - .dcrs() - .clear_bit() - .bl() - .bits(0b00) // 19 - .prelen() - .bits(0b00) // 7 - // CRC stripping for Type frames - .cst() - .set_bit() - // Fast Ethernet speed - .fes() - .set_bit() - // Duplex mode - .dm() - .set_bit() - // Automatic pad/CRC stripping - .acs() - .set_bit() - // Retry disable in half-duplex mode - .dr() - .set_bit() - }); - eth_mac.macecr.modify(|_, w| { - w.eipgen() - .clear_bit() - .usp() - .clear_bit() - .spen() - .clear_bit() - .dcrcc() - .clear_bit() - }); - // Set the MAC address - eth_mac.maca0lr.write(|w| { - w.addrlo().bits( - u32::from(mac.0[0]) - | (u32::from(mac.0[1]) << 8) - | (u32::from(mac.0[2]) << 16) - | (u32::from(mac.0[3]) << 24), - ) - }); - eth_mac.maca0hr.write(|w| { - w.addrhi() - .bits(u16::from(mac.0[4]) | (u16::from(mac.0[5]) << 8)) - }); - // frame filter register - eth_mac.macpfr.modify(|_, w| { - w.dntu() - .clear_bit() - .ipfe() - .clear_bit() - .vtfe() - .clear_bit() - .hpf() - .clear_bit() - .saf() - .clear_bit() - .saif() - .clear_bit() - .pcf() - .bits(0b00) - .dbf() - .clear_bit() - .pm() - .clear_bit() - .daif() - .clear_bit() - .hmc() - .clear_bit() - .huc() - .clear_bit() - // Receive All - .ra() - .clear_bit() - // Promiscuous mode - .pr() - .clear_bit() - }); - eth_mac.macwtr.write(|w| w.pwe().clear_bit()); - // Flow Control Register - eth_mac.macqtx_fcr.modify(|_, w| { - // Pause time - w.pt().bits(0x100) - }); - eth_mac.macrx_fcr.modify(|_, w| w); - eth_mtl.mtlrx_qomr.modify(|_, w| { - w - // Receive store and forward - .rsf() - .set_bit() - // Dropping of TCP/IP checksum error frames disable - .dis_tcp_ef() - .clear_bit() - // Forward error frames - .fep() - .clear_bit() - // Forward undersized good packets - .fup() - .clear_bit() - }); - eth_mtl.mtltx_qomr.modify(|_, w| { - w - // Transmit store and forward - .tsf() - .set_bit() - }); - - if (phy_read(PHY_REG_ID1, eth_mac) != 0x0007) - | (phy_read(PHY_REG_ID2, eth_mac) != 0xC131) - { - error!("PHY ID error!"); - } - - phy_write(PHY_REG_BCR, PHY_REG_BCR_RESET, eth_mac); - while phy_read(PHY_REG_BCR, eth_mac) & PHY_REG_BCR_RESET - == PHY_REG_BCR_RESET - {} - phy_write_ext(PHY_REG_WUCSR, 0, eth_mac); - phy_write( - PHY_REG_BCR, - PHY_REG_BCR_AN | PHY_REG_BCR_ANRST | PHY_REG_BCR_100M, - eth_mac, - ); - /* - while phy_read(PHY_REG_BSR) & PHY_REG_BSR_UP == 0 {}; - while phy_read(PHY_REG_BSR) & PHY_REG_BSR_ANDONE == 0 {}; - while phy_read(PHY_REG_SSR) & (PHY_REG_SSR_ANDONE | PHY_REG_SSR_SPEED) - != PHY_REG_SSR_ANDONE | PHY_REG_SSR_100BASE_FD {}; - */ - - // operation mode register - eth_dma.dmamr.modify(|_, w| { - w.intm() - .bits(0b00) - // Rx Tx priority ratio 1:1 - .pr() - .bits(0b000) - .txpr() - .clear_bit() - .da() - .clear_bit() - }); - // bus mode register - eth_dma.dmasbmr.modify(|_, w| { - // Address-aligned beats - w.aal() - .set_bit() - // Fixed burst - .fb() - .set_bit() - }); - eth_dma - .dmaccr - .modify(|_, w| w.dsl().bits(0).pblx8().clear_bit().mss().bits(536)); - eth_dma.dmactx_cr.modify(|_, w| { - w - // Tx DMA PBL - .txpbl() - .bits(32) - .tse() - .clear_bit() - // Operate on second frame - .osf() - .clear_bit() - }); - - eth_dma.dmacrx_cr.modify(|_, w| { - w - // receive buffer size - .rbsz() - .bits(ETH_BUFFER_SIZE as u16) - // Rx DMA PBL - .rxpbl() - .bits(32) - // Disable flushing of received frames - .rpf() - .clear_bit() - }); - - self.rx.init(eth_dma); - self.tx.init(eth_dma); - - // Manage MAC transmission and reception - eth_mac.maccr.modify(|_, w| { - w.re() - .bit(true) // Receiver Enable - .te() - .bit(true) // Transmiter Enable - }); - eth_mtl.mtltx_qomr.modify(|_, w| w.ftq().set_bit()); - - // Ensure ring buffer descriptors have been set up in memory before - // enabling DMA engine. - cortex_m::asm::dsb(); - - // Manage DMA transmission and reception - eth_dma.dmactx_cr.modify(|_, w| w.st().set_bit()); - eth_dma.dmacrx_cr.modify(|_, w| w.sr().set_bit()); - - eth_dma - .dmacsr - .modify(|_, w| w.tps().set_bit().rps().set_bit()); - } -} - -impl<'a, 'b> phy::Device<'a> for &'b mut Device { - type RxToken = RxToken<'a>; - type TxToken = TxToken<'a>; - - fn capabilities(&self) -> phy::DeviceCapabilities { - let mut capabilities = phy::DeviceCapabilities::default(); - // ethernet frame type II (6 smac, 6 dmac, 2 ethertype), - // sans CRC (4), 1500 IP MTU - capabilities.max_transmission_unit = 1514; - capabilities.max_burst_size = Some(self.tx.desc_buf.len()); - capabilities - } - - fn receive(&mut self) -> Option<(RxToken, TxToken)> { - // Skip all queued packets with errors. - while self.rx.buf_owned() && !self.rx.buf_valid() { - self.rx.buf_release() - } - - if !(self.rx.buf_owned() && self.tx.buf_owned()) { - return None; - } - - Some((RxToken(&mut self.rx), TxToken(&mut self.tx))) - } - - fn transmit(&mut self) -> Option { - if !self.tx.buf_owned() { - return None; - } - - Some(TxToken(&mut self.tx)) - } -} - -pub struct RxToken<'a>(&'a mut RxRing); - -impl<'a> phy::RxToken for RxToken<'a> { - fn consume(self, _timestamp: Instant, f: F) -> Result - where - F: FnOnce(&mut [u8]) -> Result, - { - let result = f(unsafe { self.0.buf_as_slice_mut() }); - self.0.buf_release(); - result - } -} - -pub struct TxToken<'a>(&'a mut TxRing); - -impl<'a> phy::TxToken for TxToken<'a> { - fn consume(self, _timestamp: Instant, len: usize, f: F) -> Result - where - F: FnOnce(&mut [u8]) -> Result, - { - let result = f(unsafe { self.0.buf_as_slice_mut(len) }); - self.0.buf_release(); - result - } -} diff --git a/src/i2c.rs b/src/i2c.rs deleted file mode 100644 index 9c431ca..0000000 --- a/src/i2c.rs +++ /dev/null @@ -1,145 +0,0 @@ -use stm32h7::stm32h743 as pac; - -// Adapted from stm32h7xx embedded-hal - -// I2C error -#[derive(Debug)] -pub enum Error { - // Bus error - Bus, - // Arbitration loss - Arbitration, - // Address not ACKd within a reasonable time (no device present?) - Timeout, - // Unexpected NACK during transfer - NAck, -} - -// Maximum number of times to retry NACKed address phase before timing out -// Note that many devices indicate a busy condition by NACKing (e.g. 24xx -// EEPROMs during write) -const N_RETRY: usize = 100; // ~ 10ms @ 100 kHz bus clock - -pub fn setup(i2c: &pac::I2C2) { - // Disable the peripheral before setting timings - i2c.cr1.modify(|_, w| w.pe().clear_bit()); - - // Values from STM32MXCube for 100 kHz I2C clock with 100 MHz peripheral clock - i2c.timingr.modify(|_, w| { - w.presc() - .bits(1) - .scldel() - .bits(0x12) - .sdadel() - .bits(0) - .sclh() - .bits(0xec) - .scll() - .bits(0xff) - }); - - // Enable the peripheral - i2c.cr1.write(|w| w.pe().set_bit()); -} - -// Busy-wait for a flag to be asserted, erroring out on unrecoverable problems -macro_rules! busy_wait_errors { - ($i2c:expr, $flag:ident) => { - loop { - let isr = $i2c.isr.read(); - - if isr.berr().bit_is_set() { - return Err(Error::Bus); - } else if isr.arlo().bit_is_set() { - return Err(Error::Arbitration); - } else if isr.nackf().bit_is_set() { - return Err(Error::NAck); - } else if isr.$flag().bit_is_set() { - break; - } - } - }; -} - -fn poll_for_start_ack( - i2c: &pac::I2C2, - addr: u8, - r_wn: bool, - data_len: usize, - autoend: bool, - start: bool, -) -> Result<(), Error> { - for _i in 0..N_RETRY { - // START and prepare to send `data_len` - i2c.cr2.write(|w| { - w.start() - .bit(start) - .sadd() - .bits(addr as u16) - .add10() - .clear_bit() - .rd_wrn() - .bit(r_wn) - .nbytes() - .bits(data_len as u8) - .autoend() - .bit(autoend) - }); - - loop { - let isr = i2c.isr.read(); - - if isr.berr().bit_is_set() { - return Err(Error::Bus); - } else if isr.arlo().bit_is_set() { - return Err(Error::Arbitration); - } else if isr.nackf().bit_is_set() { - i2c.icr.write(|w| w.nackcf().set_bit()); - // Wait to finish handling NACK-STOP - loop { - if i2c.isr.read().busy().bit_is_clear() { - break; - } - } - break; - } else if isr.txis().bit_is_set() || isr.rxne().bit_is_set() { - return Ok(()); - } - } - } - - Err(Error::Timeout) -} - -pub fn write_read( - i2c: &pac::I2C2, - addr: u8, - bytes: &[u8], - buffer: &mut [u8], -) -> Result<(), Error> { - assert!(bytes.len() < 256 && !bytes.is_empty()); - assert!(buffer.len() < 256 && !buffer.is_empty()); - - poll_for_start_ack(i2c, addr, false, bytes.len(), false, true)?; - - for byte in bytes { - // Wait until we are allowed to send data (START has been ACKed or last - // byte when through) - busy_wait_errors!(i2c, txis); - i2c.txdr.write(|w| w.txdata().bits(*byte)); - } - - // Wait until the last transmission is finished - busy_wait_errors!(i2c, tc); - - poll_for_start_ack(i2c, addr | 1, true, buffer.len(), true, true)?; - - for byte in buffer { - // Wait until we have received something - busy_wait_errors!(i2c, rxne); - *byte = i2c.rxdr.read().rxdata().bits(); - } - - // automatic STOP - Ok(()) -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f7e9f94..0000000 --- a/src/main.rs +++ /dev/null @@ -1,422 +0,0 @@ -#![deny(warnings)] -#![allow(clippy::missing_safety_doc)] -#![no_std] -#![no_main] -#![cfg_attr(feature = "nightly", feature(asm))] -// Enable returning `!` -#![cfg_attr(feature = "nightly", feature(never_type))] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] - -#[inline(never)] -#[panic_handler] -#[cfg(all(feature = "nightly", not(feature = "semihosting")))] -fn panic(_info: &core::panic::PanicInfo) -> ! { - let gpiod = unsafe { &*pac::GPIOD::ptr() }; - gpiod.odr.modify(|_, w| w.odr6().high().odr12().high()); // FP_LED_1, FP_LED_3 - unsafe { - core::intrinsics::abort(); - } -} - -#[cfg(feature = "semihosting")] -extern crate panic_semihosting; - -#[cfg(not(any(feature = "nightly", feature = "semihosting")))] -extern crate panic_halt; - -#[macro_use] -extern crate log; - -use core::ptr; -// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; -use core::fmt::Write; -use cortex_m_rt::exception; -use heapless::{consts::*, String, Vec}; -use rtfm::cyccnt::{Instant, U32Ext as _}; -use stm32h7::stm32h743 as pac; - -use smoltcp as net; - -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json_core::{de::from_slice, ser::to_string}; - -mod eth; - -mod iir; -use iir::*; - -mod board; -mod eeprom; -mod i2c; - -#[cfg(not(feature = "semihosting"))] -fn init_log() {} - -#[cfg(feature = "semihosting")] -fn init_log() { - use cortex_m_log::log::{init as init_log, Logger}; - use cortex_m_log::printer::semihosting::{hio::HStdout, InterruptOk}; - use log::LevelFilter; - static mut LOGGER: Option>> = None; - let logger = Logger { - inner: InterruptOk::<_>::stdout().unwrap(), - level: LevelFilter::Info, - }; - let logger = unsafe { LOGGER.get_or_insert(logger) }; - - init_log(logger).unwrap(); -} - -// Pull in build information (from `built` crate) -mod build_info { - #![allow(dead_code)] - // include!(concat!(env!("OUT_DIR"), "/built.rs")); -} - -const SCALE: f32 = ((1 << 15) - 1) as f32; - -// static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true); - -const TCP_RX_BUFFER_SIZE: usize = 8192; -const TCP_TX_BUFFER_SIZE: usize = 8192; - -macro_rules! create_socket { - ($set:ident, $rx_storage:ident, $tx_storage:ident, $target:ident) => { - let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE]; - let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE]; - let tcp_rx_buffer = - net::socket::TcpSocketBuffer::new(&mut $rx_storage[..]); - let tcp_tx_buffer = - net::socket::TcpSocketBuffer::new(&mut $tx_storage[..]); - let tcp_socket = - net::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - let $target = $set.add(tcp_socket); - }; -} - -#[rtfm::app(device = stm32h7::stm32h743, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] -const APP: () = { - struct Resources { - spi: (pac::SPI1, pac::SPI2, pac::SPI4, pac::SPI5), - i2c: pac::I2C2, - ethernet_periph: - (pac::ETHERNET_MAC, pac::ETHERNET_DMA, pac::ETHERNET_MTL), - #[init([[0.; 5]; 2])] - iir_state: [IIRState; 2], - #[init([IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; 2])] - iir_ch: [IIR; 2], - #[link_section = ".sram3.eth"] - #[init(eth::Device::new())] - ethernet: eth::Device, - } - - #[init(schedule = [tick])] - fn init(c: init::Context) -> init::LateResources { - board::init(); - - init_log(); - // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); - // info!("Built on {}", build_info::BUILT_TIME_UTC); - // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); - - // c.schedule.tick(Instant::now()).unwrap(); - - let dp = c.device; - init::LateResources { - spi: (dp.SPI1, dp.SPI2, dp.SPI4, dp.SPI5), - i2c: dp.I2C2, - ethernet_periph: ( - dp.ETHERNET_MAC, - dp.ETHERNET_DMA, - dp.ETHERNET_MTL, - ), - } - } - - #[idle(resources = [ethernet, ethernet_periph, iir_state, iir_ch, i2c])] - fn idle(c: idle::Context) -> ! { - let (MAC, DMA, MTL) = c.resources.ethernet_periph; - - let hardware_addr = match eeprom::read_eui48(c.resources.i2c) { - Err(_) => { - info!("Could not read EEPROM, using default MAC address"); - net::wire::EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]) - } - Ok(raw_mac) => net::wire::EthernetAddress(raw_mac), - }; - info!("MAC: {}", hardware_addr); - - unsafe { c.resources.ethernet.init(hardware_addr, MAC, DMA, MTL) }; - let mut neighbor_cache_storage = [None; 8]; - let neighbor_cache = - net::iface::NeighborCache::new(&mut neighbor_cache_storage[..]); - let local_addr = net::wire::IpAddress::v4(10, 0, 16, 99); - let mut ip_addrs = [net::wire::IpCidr::new(local_addr, 24)]; - let mut iface = - net::iface::EthernetInterfaceBuilder::new(c.resources.ethernet) - .ethernet_addr(hardware_addr) - .neighbor_cache(neighbor_cache) - .ip_addrs(&mut ip_addrs[..]) - .finalize(); - let mut socket_set_entries: [_; 8] = Default::default(); - let mut sockets = - net::socket::SocketSet::new(&mut socket_set_entries[..]); - create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle0); - create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle1); - - // unsafe { eth::enable_interrupt(DMA); } - let mut time = 0u32; - let mut next_ms = Instant::now(); - next_ms += 400_000.cycles(); - let mut server = Server::new(); - let mut iir_state: resources::iir_state = c.resources.iir_state; - let mut iir_ch: resources::iir_ch = c.resources.iir_ch; - loop { - // if ETHERNET_PENDING.swap(false, Ordering::Relaxed) { } - let tick = Instant::now() > next_ms; - if tick { - next_ms += 400_000.cycles(); - time += 1; - } - { - let socket = - &mut *sockets.get::(tcp_handle0); - if socket.state() == net::socket::TcpState::CloseWait { - socket.close(); - } else if !(socket.is_open() || socket.is_listening()) { - socket - .listen(1234) - .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); - } else if tick && socket.can_send() { - let s = iir_state.lock(|iir_state| Status { - t: time, - x0: iir_state[0][0], - y0: iir_state[0][2], - x1: iir_state[1][0], - y1: iir_state[1][2], - }); - json_reply(socket, &s); - } - } - { - let socket = - &mut *sockets.get::(tcp_handle1); - if socket.state() == net::socket::TcpState::CloseWait { - socket.close(); - } else if !(socket.is_open() || socket.is_listening()) { - socket - .listen(1235) - .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); - } else { - server.poll(socket, |req: &Request| { - if req.channel < 2 { - iir_ch.lock(|iir_ch| { - iir_ch[req.channel as usize] = req.iir - }); - } - }); - } - } - - if !match iface.poll( - &mut sockets, - net::time::Instant::from_millis(time as i64), - ) { - Ok(changed) => changed, - Err(net::Error::Unrecognized) => true, - Err(e) => { - info!("iface poll error: {:?}", e); - true - } - } { - // cortex_m::asm::wfi(); - } - } - } - - #[task(priority = 1, schedule = [tick])] - fn tick(c: tick::Context) { - static mut TIME: u32 = 0; - *TIME += 1; - const PERIOD: u32 = 200_000_000; - c.schedule.tick(c.scheduled + PERIOD.cycles()).unwrap(); - } - - // seems to slow it down - // #[link_section = ".data.spi1"] - #[task(binds = SPI1, resources = [spi, iir_state, iir_ch], priority = 2)] - fn spi1(c: spi1::Context) { - #[cfg(feature = "bkpt")] - cortex_m::asm::bkpt(); - let (spi1, spi2, spi4, spi5) = c.resources.spi; - let iir_ch = c.resources.iir_ch; - let iir_state = c.resources.iir_state; - - let sr = spi1.sr.read(); - if sr.eot().bit_is_set() { - spi1.ifcr.write(|w| w.eotc().set_bit()); - } - if sr.rxp().bit_is_set() { - let rxdr = &spi1.rxdr as *const _ as *const u16; - let a = unsafe { ptr::read_volatile(rxdr) }; - let x0 = f32::from(a as i16); - let y0 = iir_ch[0].update(&mut iir_state[0], x0); - let d = y0 as i16 as u16 ^ 0x8000; - let txdr = &spi2.txdr as *const _ as *mut u16; - unsafe { ptr::write_volatile(txdr, d) }; - } - - let sr = spi5.sr.read(); - if sr.eot().bit_is_set() { - spi5.ifcr.write(|w| w.eotc().set_bit()); - } - if sr.rxp().bit_is_set() { - let rxdr = &spi5.rxdr as *const _ as *const u16; - let a = unsafe { ptr::read_volatile(rxdr) }; - let x0 = f32::from(a as i16); - let y0 = iir_ch[1].update(&mut iir_state[1], x0); - let d = y0 as i16 as u16 ^ 0x8000; - let txdr = &spi4.txdr as *const _ as *mut u16; - unsafe { ptr::write_volatile(txdr, d) }; - } - #[cfg(feature = "bkpt")] - cortex_m::asm::bkpt(); - } - - /* - #[task(binds = ETH, resources = [ethernet_periph], priority = 1)] - fn eth(c: eth::Context) { - let dma = &c.resources.ethernet_periph.1; - ETHERNET_PENDING.store(true, Ordering::Relaxed); - unsafe { eth::interrupt_handler(dma) } - } - */ - - extern "C" { - // hw interrupt handlers for RTFM to use for scheduling tasks - // one per priority - fn DCMI(); - fn JPEG(); - fn SDMMC(); - } -}; - -#[derive(Deserialize, Serialize)] -struct Request { - channel: u8, - iir: IIR, -} - -#[derive(Serialize)] -struct Response<'a> { - code: i32, - message: &'a str, -} - -#[derive(Serialize)] -struct Status { - t: u32, - x0: f32, - y0: f32, - x1: f32, - y1: f32, -} - -fn json_reply(socket: &mut net::socket::TcpSocket, msg: &T) { - let mut u: String = to_string(msg).unwrap(); - u.push('\n').unwrap(); - socket.write_str(&u).unwrap(); -} - -struct Server { - data: Vec, - discard: bool, -} - -impl Server { - fn new() -> Self { - Self { - data: Vec::new(), - discard: false, - } - } - - fn poll( - &mut self, - socket: &mut net::socket::TcpSocket, - f: F, - ) -> Option - where - T: DeserializeOwned, - F: FnOnce(&T) -> R, - { - while socket.can_recv() { - let found = socket - .recv(|buf| { - let (len, found) = - match buf.iter().position(|&c| c as char == '\n') { - Some(end) => (end + 1, true), - None => (buf.len(), false), - }; - if self.data.len() + len >= self.data.capacity() { - self.discard = true; - self.data.clear(); - } else if !self.discard && len > 0 { - self.data.extend_from_slice(&buf[..len]).unwrap(); - } - (len, found) - }) - .unwrap(); - if found { - if self.discard { - self.discard = false; - json_reply( - socket, - &Response { - code: 520, - message: "command buffer overflow", - }, - ); - self.data.clear(); - } else { - let r = from_slice::(&self.data[..self.data.len() - 1]); - self.data.clear(); - match r { - Ok(res) => { - let r = f(&res); - json_reply( - socket, - &Response { - code: 200, - message: "ok", - }, - ); - return Some(r); - } - Err(err) => { - warn!("parse error {:?}", err); - json_reply( - socket, - &Response { - code: 550, - message: "parse error", - }, - ); - } - } - } - } - } - None - } -} - -#[exception] -fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { - panic!("HardFault at {:#?}", ef); -} - -#[exception] -fn DefaultHandler(irqn: i16) { - panic!("Unhandled exception (IRQn = {})", irqn); -} diff --git a/stabilizer/Cargo.lock b/stabilizer/Cargo.lock new file mode 100644 index 0000000..f6e7f56 --- /dev/null +++ b/stabilizer/Cargo.lock @@ -0,0 +1,583 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ad9959" +version = "0.1.0" +source = "git+https://github.com/quartiq/ad9959.git?branch=feature/basic-driver#b45bce3f0dd7a58b5a272b778001dc9422c2c160" +dependencies = [ + "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aligned" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "aligned" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "as-slice" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "asm-delay" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitrate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit_field" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitrate" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cortex-m" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aligned 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-log" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rt" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rtfm" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rtfm-macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "heapless 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rtfm-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rtfm-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rtfm-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-semihosting" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "embedded-hal" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum-iterator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "enum-iterator-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hash32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heapless" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "managed" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mcp23017" +version = "0.1.1" +source = "git+https://github.com/mrd0ll4r/mcp23017.git#a3d072754abca60a92ece820f7cfb767a0c11669" +dependencies = [ + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "panic-halt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "panic-semihosting" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "paste" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "paste-impl 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "paste-impl" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "r0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rtfm-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rtfm-syntax" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indexmap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde-json-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heapless 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smoltcp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stabilizer" +version = "0.3.0" +dependencies = [ + "ad9959 0.1.0 (git+https://github.com/quartiq/ad9959.git?branch=feature/basic-driver)", + "asm-delay 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-log 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rtfm 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "enum-iterator 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "heapless 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mcp23017 0.1.1 (git+https://github.com/mrd0ll4r/mcp23017.git)", + "nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "panic-semihosting 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-json-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stm32h7-ethernet 0.1.1 (git+https://github.com/quartiq/stm32h7-ethernet.git)", + "stm32h7xx-hal 0.5.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stm32h7" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stm32h7-ethernet" +version = "0.1.1" +source = "git+https://github.com/quartiq/stm32h7-ethernet.git#cf9b8bb2e1b440d8ada6ac6048f48dc4ed9c269a" +dependencies = [ + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stm32h7xx-hal 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stm32h7xx-hal" +version = "0.5.0" +dependencies = [ + "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "stm32h7 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stm32h7xx-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "stm32h7 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vcell" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "volatile-register" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum ad9959 0.1.0 (git+https://github.com/quartiq/ad9959.git?branch=feature/basic-driver)" = "" +"checksum aligned 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d39da9b88ae1a81c03c9c082b8db83f1d0e93914126041962af61034ab44c4a5" +"checksum aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" +"checksum as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" +"checksum asm-delay 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e0c8eec73de29ae94b2aff405a272304bc286204ddb1cdf20d7e2249078ae20" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +"checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum bitrate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum cortex-m 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0b159a1e8306949579de3698c841dba58058197b65c60807194e4fa1e7a554" +"checksum cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" +"checksum cortex-m-log 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "978caafe65d1023d38b00c76b83564788fc351d954a5005fb72cf992c0d61458" +"checksum cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" +"checksum cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" +"checksum cortex-m-rtfm 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eaf0b9fd3f042cb3793d15daf3cea201b2f25c99b0b5b936a551bb6909c3ae5b" +"checksum cortex-m-rtfm-macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c62092f6ff344e9b0adb748f0302ed69889ba2fae1fce446e3788d4726ea73bb" +"checksum cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" +"checksum embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b" +"checksum enum-iterator 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" +"checksum enum-iterator-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" +"checksum hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" +"checksum heapless 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0" +"checksum indexmap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" +"checksum mcp23017 0.1.1 (git+https://github.com/mrd0ll4r/mcp23017.git)" = "" +"checksum nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc" +"checksum panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" +"checksum panic-semihosting 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c" +"checksum paste 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" +"checksum paste-impl 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" +"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum rtfm-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec893edb2aa5b70320b94896ffea22a7ebb1cf3f942bb67cd5b60a865a63493" +"checksum rtfm-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4455e23c34df3d66454e7e218a4d76a7f83321d04a806be614463341cec4116e" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)" = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +"checksum serde-json-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf406405ada9ef326ca78677324ac66994ff348fc48a16030be08caeed29825" +"checksum serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +"checksum smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum stm32h7 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9beb5e2a223c82f263c3051bba4614aebc6e98bd40217df3cd8817c83ac7bd8" +"checksum stm32h7-ethernet 0.1.1 (git+https://github.com/quartiq/stm32h7-ethernet.git)" = "" +"checksum stm32h7xx-hal 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "987c66628f30012ed9a41cc738421c5caece03292c0cc8fd1e99956f122735bd" +"checksum syn 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" +"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" diff --git a/stabilizer/Cargo.toml b/stabilizer/Cargo.toml new file mode 100644 index 0000000..6db769f --- /dev/null +++ b/stabilizer/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "stabilizer" +version = "0.3.0" +authors = ["Robert Jördens "] +description = "Firmware for the Sinara Stabilizer board (stm32h743, eth, poe, 2 adc, 2 dac)" +categories = ["embedded", "no-std", "hardware-support", "science"] +license = "GPL-3.0-or-later" +keywords = ["ethernet", "stm32h7", "adc", "dac", "physics"] +repository = "https://github.com/quartiq/stabilizer" +readme = "README.md" +documentation = "https://docs.rs/stabilizer/" +edition = "2018" +exclude = [ + ".travis.yml", + ".gitignore", + "doc/", + "doc/*" +] + +[badges] +travis-ci = { repository = "quartiq/stabilizer", branch = "master" } +maintenance = { status = "experimental" } + +[package.metadata.docs.rs] +features = [] +default-target = "thumbv7em-none-eabihf" + +[dependencies] +cortex-m = { version = "0.6", features = ["const-fn"] } +cortex-m-rt = { version = "0.6", features = ["device"] } +cortex-m-log = { version = "0.6", features = ["log-integration"] } +log = "0.4" +panic-semihosting = { version = "0.5", optional = true } +panic-halt = "0.2" +serde = { version = "1.0", features = ["derive"], default-features = false } +heapless = { version = "0.5", features = ["serde"] } +serde-json-core = "0.1" +cortex-m-rtfm = "0.5" +embedded-hal = "0.2.3" +nb = "0.1.2" +asm-delay = "0.7.0" +enum-iterator = "0.6.0" + +[dependencies.mcp23017] +git = "https://github.com/mrd0ll4r/mcp23017.git" + +[dependencies.smoltcp] +version = "0.6" +features = ["ethernet", "proto-ipv4", "socket-tcp", "proto-ipv6"] +default-features = false + +[dependencies.ad9959] +path = "../ad9959" + +[dependencies.stm32h7-ethernet] +git = "https://github.com/quartiq/stm32h7-ethernet.git" +features = ["stm32h743v"] + +[dependencies.stm32h7xx-hal] +path = "../stm32h7xx-hal/" +features = ["stm32h743v", "rt", "unproven"] + +[features] +semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] +bkpt = [ ] +nightly = ["cortex-m/inline-asm"] + diff --git a/stabilizer/src/afe.rs b/stabilizer/src/afe.rs new file mode 100644 index 0000000..62d9cb4 --- /dev/null +++ b/stabilizer/src/afe.rs @@ -0,0 +1,83 @@ +use embedded_hal; +use serde::{Serialize, Deserialize}; + +use core::convert::TryFrom; +use enum_iterator::IntoEnumIterator; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, IntoEnumIterator)] +pub enum Gain { + G1 = 0b00, + G2 = 0b01, + G5 = 0b10, + G10 = 0b11 +} + +/// A programmable gain amplifier that allows for setting the gain via GPIO. +pub struct ProgrammableGainAmplifier { + a0: A0, + a1: A1 +} + +impl TryFrom for Gain { + type Error = (); + + fn try_from(value: u8) -> Result { + for gain in Gain::into_enum_iter() { + if value == gain as u8 { + return Ok(gain) + } + } + + Err(()) + } +} + +impl ProgrammableGainAmplifier +where + A0: embedded_hal::digital::v2::StatefulOutputPin, + A0::Error: core::fmt::Debug, + A1: embedded_hal::digital::v2::StatefulOutputPin, + A1::Error: core::fmt::Debug, +{ + /// Construct a new programmable gain driver. + /// + /// Args: + /// * `a0` - An output connected to the A0 input of the amplifier. + /// * `a1` - An output connected to the A1 input of the amplifier. + pub fn new(a0: A0, a1: A1) -> Self + { + let mut afe = Self { a0: a0, a1: a1}; + + afe.set_gain(Gain::G1); + + afe + } + + /// Set the gain of the front-end. + pub fn set_gain(&mut self, gain: Gain) { + if (gain as u8 & 0b01) != 0 { + self.a0.set_high().unwrap(); + } else { + self.a0.set_low().unwrap(); + } + + if (gain as u8 & 0b10) != 0 { + self.a1.set_high().unwrap() + } else { + self.a1.set_low().unwrap(); + } + } + + /// Get the programmed gain of the analog front-end. + pub fn get_gain(&self) -> Result { + let mut code: u8 = 0; + if self.a0.is_set_high().unwrap() { + code |= 0b1; + } + if self.a1.is_set_high().unwrap() { + code |= 0b10; + } + + Gain::try_from(code) + } +} diff --git a/stabilizer/src/eeprom.rs b/stabilizer/src/eeprom.rs new file mode 100644 index 0000000..a31a4ff --- /dev/null +++ b/stabilizer/src/eeprom.rs @@ -0,0 +1,12 @@ +use embedded_hal::blocking::i2c::WriteRead; + +const I2C_ADDR: u8 = 0x50; + +#[allow(dead_code)] +pub fn read_eui48(i2c: &mut T) -> Result<[u8; 6], T::Error> +where T: WriteRead +{ + let mut buffer = [0u8; 6]; + i2c.write_read(I2C_ADDR, &[0xFA_u8], &mut buffer)?; + Ok(buffer) +} diff --git a/src/iir.rs b/stabilizer/src/iir.rs similarity index 100% rename from src/iir.rs rename to stabilizer/src/iir.rs diff --git a/stabilizer/src/main.rs b/stabilizer/src/main.rs new file mode 100644 index 0000000..a903717 --- /dev/null +++ b/stabilizer/src/main.rs @@ -0,0 +1,738 @@ +#![deny(warnings)] +#![allow(clippy::missing_safety_doc)] +#![no_std] +#![no_main] +#![cfg_attr(feature = "nightly", feature(asm))] +// Enable returning `!` +#![cfg_attr(feature = "nightly", feature(never_type))] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] + +#[inline(never)] +#[panic_handler] +#[cfg(all(feature = "nightly", not(feature = "semihosting")))] +fn panic(_info: &core::panic::PanicInfo) -> ! { + let gpiod = unsafe { &*hal::stm32::GPIOD::ptr() }; + gpiod.odr.modify(|_, w| w.odr6().high().odr12().high()); // FP_LED_1, FP_LED_3 + unsafe { + core::intrinsics::abort(); + } +} + +#[cfg(feature = "semihosting")] +extern crate panic_semihosting; + +#[cfg(not(any(feature = "nightly", feature = "semihosting")))] +extern crate panic_halt; + +#[macro_use] +extern crate log; + +// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; +use asm_delay; +use rtfm::cyccnt::{Instant, U32Ext}; +use cortex_m_rt::exception; +use cortex_m; +use stm32h7xx_hal as hal; +use stm32h7xx_hal::{ + prelude::*, +}; + +use embedded_hal::{ + digital::v2::OutputPin, +}; + +use stm32h7_ethernet as ethernet; +use smoltcp as net; + +use heapless::{ + String, + consts::*, +}; + +#[link_section = ".sram3.eth"] +static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); + +mod afe; +mod eeprom; +mod iir; +mod pounder; +mod server; + +#[cfg(not(feature = "semihosting"))] +fn init_log() {} + +#[cfg(feature = "semihosting")] +fn init_log() { + use cortex_m_log::log::{init as init_log, Logger}; + use cortex_m_log::printer::semihosting::{hio::HStdout, InterruptOk}; + use log::LevelFilter; + static mut LOGGER: Option>> = None; + let logger = Logger { + inner: InterruptOk::<_>::stdout().unwrap(), + level: LevelFilter::Info, + }; + let logger = unsafe { LOGGER.get_or_insert(logger) }; + + init_log(logger).unwrap(); +} + +// Pull in build information (from `built` crate) +mod build_info { + #![allow(dead_code)] + // include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +pub struct NetStorage { + ip_addrs: [net::wire::IpCidr; 1], + neighbor_cache: [Option<(net::wire::IpAddress, net::iface::Neighbor)>; 8], +} + +static mut NET_STORE: NetStorage = NetStorage { + // Placeholder for the real IP address, which is initialized at runtime. + ip_addrs: [net::wire::IpCidr::Ipv6(net::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX)], + + neighbor_cache: [None; 8], +}; + +const SCALE: f32 = ((1 << 15) - 1) as f32; + +const SPI_START_CODE: u32 = 0x201; + +// static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true); + +const TCP_RX_BUFFER_SIZE: usize = 8192; +const TCP_TX_BUFFER_SIZE: usize = 8192; + +type AFE1 = afe::ProgrammableGainAmplifier< + hal::gpio::gpiof::PF2>, + hal::gpio::gpiof::PF5>>; + +type AFE2 = afe::ProgrammableGainAmplifier< + hal::gpio::gpiod::PD14>, + hal::gpio::gpiod::PD15>>; + +macro_rules! route_request { + ($request:ident, + readable_attributes: [$($read_attribute:tt: $getter:tt),*], + modifiable_attributes: [$($write_attribute:tt: $TYPE:ty, $setter:tt),*]) => { + match $request.req { + server::AccessRequest::Read => { + match $request.attribute { + $( + $read_attribute => { + let value = match $getter() { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to read attribute"), + }; + + let encoded_data: String = match serde_json_core::to_string(&value) { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to encode attribute value"), + }; + + server::Response::success($request.attribute, &encoded_data) + }, + )* + _ => server::Response::error($request.attribute, "Unknown attribute") + } + }, + server::AccessRequest::Write => { + match $request.attribute { + $( + $write_attribute => { + let new_value = match serde_json_core::from_str::<$TYPE>(&$request.value) { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to decode value"), + }; + + match $setter(new_value) { + Ok(_) => server::Response::success($request.attribute, &$request.value), + Err(_) => server::Response::error($request.attribute, + "Failed to set attribute"), + } + } + )* + _ => server::Response::error($request.attribute, "Unknown attribute") + } + } + } + } +} + +#[rtfm::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] +const APP: () = { + struct Resources { + adc1: hal::spi::Spi, + dac1: hal::spi::Spi, + afe1: AFE1, + + adc2: hal::spi::Spi, + dac2: hal::spi::Spi, + afe2: AFE2, + + eeprom_i2c: hal::i2c::I2c, + + timer: hal::timer::Timer, + net_interface: net::iface::EthernetInterface<'static, 'static, 'static, + ethernet::EthernetDMA<'static>>, + eth_mac: ethernet::EthernetMAC, + mac_addr: net::wire::EthernetAddress, + + pounder: pounder::PounderDevices, + + #[init([[0.; 5]; 2])] + iir_state: [iir::IIRState; 2], + #[init([iir::IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; 2])] + iir_ch: [iir::IIR; 2], + } + + #[init] + fn init(c: init::Context) -> init::LateResources { + let dp = c.device; + let mut cp = cortex_m::Peripherals::take().unwrap(); + + let pwr = dp.PWR.constrain(); + let vos = pwr.freeze(); + + let rcc = dp.RCC.constrain(); + let mut clocks = rcc + //TODO: Re-enable HSE for Stabilizer platform. +// .use_hse(8.mhz()) + .sysclk(400.mhz()) + .hclk(200.mhz()) + .per_ck(100.mhz()) + .pll2_p_ck(100.mhz()) + .pll2_q_ck(100.mhz()) + .freeze(vos, &dp.SYSCFG); + + init_log(); + + // Enable SRAM3 for the ethernet descriptor ring. + clocks.rb.ahb2enr.modify(|_, w| w.sram3en().set_bit()); + + clocks.rb.rsr.write(|w| w.rmvf().set_bit()); + + clocks.rb.d2ccip1r.modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q()); + + let mut delay = hal::delay::Delay::new(cp.SYST, clocks.clocks); + + let gpioa = dp.GPIOA.split(&mut clocks); + let gpiob = dp.GPIOB.split(&mut clocks); + let gpioc = dp.GPIOC.split(&mut clocks); + let gpiod = dp.GPIOD.split(&mut clocks); + let gpioe = dp.GPIOE.split(&mut clocks); + let gpiof = dp.GPIOF.split(&mut clocks); + let gpiog = dp.GPIOG.split(&mut clocks); + + let afe1 = { + let a0_pin = gpiof.pf2.into_push_pull_output(); + let a1_pin = gpiof.pf5.into_push_pull_output(); + afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) + }; + + let afe2 = { + let a0_pin = gpiod.pd14.into_push_pull_output(); + let a1_pin = gpiod.pd15.into_push_pull_output(); + afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) + }; + + // Configure the SPI interfaces to the ADCs and DACs. + let adc1_spi = { + let spi_miso = gpiob.pb14.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpiob.pb10.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let _spi_nss = gpiob.pb9.into_alternate_af5(); + + let config = hal::spi::Config::new(hal::spi::Mode{ + polarity: hal::spi::Polarity::IdleHigh, + phase: hal::spi::Phase::CaptureOnSecondTransition, + }) + .communication_mode(hal::spi::CommunicationMode::Receiver) + .manage_cs() + .transfer_size(1) + .frame_size(16) + .cs_delay(220e-9); + + let mut spi = dp.SPI2.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + 50.mhz(), + &clocks); + + spi.listen(hal::spi::Event::Eot); + + spi + }; + + let adc2_spi = { + let spi_miso = gpiob.pb4.into_alternate_af6().set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpioc.pc10.into_alternate_af6().set_speed(hal::gpio::Speed::VeryHigh); + let _spi_nss = gpioa.pa15.into_alternate_af6(); + + + let config = hal::spi::Config::new(hal::spi::Mode{ + polarity: hal::spi::Polarity::IdleHigh, + phase: hal::spi::Phase::CaptureOnSecondTransition, + }) + .communication_mode(hal::spi::CommunicationMode::Receiver) + .manage_cs() + .transfer_size(1) + .frame_size(16) + .cs_delay(220e-9); + + let mut spi = dp.SPI3.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + 50.mhz(), + &clocks); + + spi.listen(hal::spi::Event::Eot); + + spi + }; + + let dac1_spi = { + let spi_miso = gpioe.pe5.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpioe.pe2.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let _spi_nss = gpioe.pe4.into_alternate_af5(); + + let config = hal::spi::Config::new(hal::spi::Mode{ + polarity: hal::spi::Polarity::IdleHigh, + phase: hal::spi::Phase::CaptureOnSecondTransition, + }) + .communication_mode(hal::spi::CommunicationMode::Transmitter) + .manage_cs() + .transfer_size(1) + .frame_size(16) + .swap_mosi_miso(); + + let spi = dp.SPI4.spi((spi_sck, spi_miso, hal::spi::NoMosi), config, 50.mhz(), &clocks); + spi + }; + + let dac2_spi = { + let spi_miso = gpiof.pf8.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpiof.pf7.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let _spi_nss = gpiof.pf6.into_alternate_af5(); + + let config = hal::spi::Config::new(hal::spi::Mode{ + polarity: hal::spi::Polarity::IdleHigh, + phase: hal::spi::Phase::CaptureOnSecondTransition, + }) + .communication_mode(hal::spi::CommunicationMode::Transmitter) + .manage_cs() + .transfer_size(1) + .frame_size(16) + .swap_mosi_miso(); + + let spi = dp.SPI5.spi((spi_sck, spi_miso, hal::spi::NoMosi), config, 50.mhz(), &clocks); + + spi + }; + + let mut fp_led_0 = gpiod.pd5.into_push_pull_output(); + let mut fp_led_1 = gpiod.pd6.into_push_pull_output(); + let mut fp_led_2 = gpiog.pg4.into_push_pull_output(); + let mut fp_led_3 = gpiod.pd12.into_push_pull_output(); + + fp_led_0.set_low().unwrap(); + fp_led_1.set_low().unwrap(); + fp_led_2.set_low().unwrap(); + fp_led_3.set_low().unwrap(); + + + let pounder_devices = { + let ad9959 = { + let qspi_interface = { + // Instantiate the QUADSPI pins and peripheral interface. + // TODO: Place these into a pins structure that is provided to the QSPI + // constructor. + let _qspi_clk = gpiob.pb2.into_alternate_af9().set_speed(hal::gpio::Speed::VeryHigh); + let _qspi_ncs = gpioc.pc11.into_alternate_af9().set_speed(hal::gpio::Speed::VeryHigh); + let _qspi_io0 = gpioe.pe7.into_alternate_af10().set_speed(hal::gpio::Speed::VeryHigh); + let _qspi_io1 = gpioe.pe8.into_alternate_af10().set_speed(hal::gpio::Speed::VeryHigh); + let _qspi_io2 = gpioe.pe9.into_alternate_af10().set_speed(hal::gpio::Speed::VeryHigh); + let _qspi_io3 = gpioe.pe10.into_alternate_af10().set_speed(hal::gpio::Speed::VeryHigh); + + let qspi = hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 10.mhz()).unwrap(); + pounder::QspiInterface::new(qspi).unwrap() + }; + + let mut reset_pin = gpioa.pa0.into_push_pull_output(); + let io_update = gpiog.pg7.into_push_pull_output(); + + + let asm_delay = { + let frequency_hz = clocks.clocks.c_ck().0; + asm_delay::AsmDelay::new(asm_delay::bitrate::Hertz (frequency_hz)) + }; + + ad9959::Ad9959::new(qspi_interface, + &mut reset_pin, + io_update, + asm_delay, + ad9959::Mode::FourBitSerial, + 100_000_000f32, + 5).unwrap() + }; + + let io_expander = { + let sda = gpiob.pb7.into_alternate_af4().set_open_drain(); + let scl = gpiob.pb8.into_alternate_af4().set_open_drain(); + let i2c1 = dp.I2C1.i2c((scl, sda), 100.khz(), &clocks); + mcp23017::MCP23017::default(i2c1).unwrap() + }; + + let spi = { + let spi_mosi = gpiod.pd7.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let spi_miso = gpioa.pa6.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpiog.pg11.into_alternate_af5().set_speed(hal::gpio::Speed::VeryHigh); + + let config = hal::spi::Config::new(hal::spi::Mode{ + polarity: hal::spi::Polarity::IdleHigh, + phase: hal::spi::Phase::CaptureOnSecondTransition, + }) + .frame_size(8); + + // The maximum frequency of this SPI must be limited due to capacitance on the MISO + // line causing a long RC decay. + dp.SPI1.spi((spi_sck, spi_miso, spi_mosi), config, 5.mhz(), &clocks) + }; + + let adc1 = { + let mut adc = dp.ADC1.adc(&mut delay, &mut clocks); + adc.calibrate(); + + adc.enable() + }; + + let adc2 = { + let mut adc = dp.ADC2.adc(&mut delay, &mut clocks); + adc.calibrate(); + + adc.enable() + }; + + let adc1_in_p = gpiof.pf11.into_analog(); + let adc2_in_p = gpiof.pf14.into_analog(); + + pounder::PounderDevices::new(io_expander, + ad9959, + spi, + adc1, + adc2, + adc1_in_p, + adc2_in_p).unwrap() + }; + + let mut eeprom_i2c = { + let sda = gpiof.pf0.into_alternate_af4().set_open_drain(); + let scl = gpiof.pf1.into_alternate_af4().set_open_drain(); + dp.I2C2.i2c((scl, sda), 100.khz(), &clocks) + }; + + // Configure ethernet pins. + { + // Reset the PHY before configuring pins. + let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output(); + eth_phy_nrst.set_low().unwrap(); + delay.delay_us(200u8); + eth_phy_nrst.set_high().unwrap(); + let _rmii_ref_clk = gpioa.pa1.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_mdio = gpioa.pa2.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_mdc = gpioc.pc1.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_crs_dv = gpioa.pa7.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_rxd0 = gpioc.pc4.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_rxd1 = gpioc.pc5.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_tx_en = gpiob.pb11.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_txd0 = gpiob.pb12.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_txd1 = gpiog.pg14.into_alternate_af11().set_speed(hal::gpio::Speed::VeryHigh); + } + + let mac_addr = match eeprom::read_eui48(&mut eeprom_i2c) { + Err(_) => { + info!("Could not read EEPROM, using default MAC address"); + net::wire::EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]) + } + Ok(raw_mac) => net::wire::EthernetAddress(raw_mac), + }; + + let (network_interface, eth_mac) = { + // Configure the ethernet controller + let (eth_dma, eth_mac) = unsafe { + ethernet::ethernet_init( + dp.ETHERNET_MAC, + dp.ETHERNET_MTL, + dp.ETHERNET_DMA, + &mut DES_RING, + mac_addr.clone()) + }; + + unsafe { ethernet::enable_interrupt() }; + + let store = unsafe { &mut NET_STORE }; + + store.ip_addrs[0] = net::wire::IpCidr::new(net::wire::IpAddress::v4(10, 0, 16, 99), 24); + + let neighbor_cache = net::iface::NeighborCache::new(&mut store.neighbor_cache[..]); + + let interface = net::iface::EthernetInterfaceBuilder::new(eth_dma) + .ethernet_addr(mac_addr) + .neighbor_cache(neighbor_cache) + .ip_addrs(&mut store.ip_addrs[..]) + .finalize(); + + (interface, eth_mac) + }; + + cp.SCB.enable_icache(); + + // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); + // info!("Built on {}", build_info::BUILT_TIME_UTC); + // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); + + // Utilize the cycle counter for RTFM scheduling. + cp.DWT.enable_cycle_counter(); + + let mut dma = hal::dma::Dma::dma(dp.DMA1, dp.DMAMUX1, &clocks); + dma.configure_m2p_stream(hal::dma::Stream::One, + &SPI_START_CODE as *const _ as u32, + &adc1_spi.spi.cr1 as *const _ as u32, + hal::dma::DMAREQ_ID::TIM2_CH1); + + dma.configure_m2p_stream(hal::dma::Stream::Two, + &SPI_START_CODE as *const _ as u32, + &adc2_spi.spi.cr1 as *const _ as u32, + hal::dma::DMAREQ_ID::TIM2_CH2); + + // Configure timer 2 to trigger conversions for the ADC + let mut timer2 = dp.TIM2.timer(500.khz(), &mut clocks); + timer2.configure_channel(hal::timer::Channel::One, 0.25); + timer2.configure_channel(hal::timer::Channel::Two, 0.75); + + timer2.listen(hal::timer::Event::ChannelOneDma); + timer2.listen(hal::timer::Event::ChannelTwoDma); + + init::LateResources { + adc1: adc1_spi, + dac1: dac1_spi, + adc2: adc2_spi, + dac2: dac2_spi, + afe1: afe1, + afe2: afe2, + + timer: timer2, + pounder: pounder_devices, + + eeprom_i2c: eeprom_i2c, + net_interface: network_interface, + eth_mac: eth_mac, + mac_addr: mac_addr, + } + } + + #[task(binds = SPI3, resources = [adc2, dac2, iir_state, iir_ch], priority = 2)] + fn spi3(c: spi3::Context) { + c.resources.adc2.spi.ifcr.write(|w| w.eotc().set_bit()); + + let output: u16 = { + let a: u16 = c.resources.adc2.read().unwrap(); + let x0 = f32::from(a as i16); + let y0 = c.resources.iir_ch[1].update(&mut c.resources.iir_state[1], x0); + y0 as i16 as u16 ^ 0x8000 + }; + + c.resources.dac2.spi.ifcr.write(|w| w.eotc().set_bit().txtfc().set_bit()); + c.resources.dac2.send(output).unwrap(); + } + + #[task(binds = SPI2, resources = [adc1, dac1, iir_state, iir_ch], priority = 2)] + fn spi2(c: spi2::Context) { + c.resources.adc1.spi.ifcr.write(|w| w.eotc().set_bit()); + + let output: u16 = { + let a: u16 = c.resources.adc1.read().unwrap(); + let x0 = f32::from(a as i16); + let y0 = c.resources.iir_ch[0].update(&mut c.resources.iir_state[0], x0); + y0 as i16 as u16 ^ 0x8000 + }; + + c.resources.dac1.spi.ifcr.write(|w| w.eotc().set_bit().txtfc().set_bit()); + c.resources.dac1.send(output).unwrap(); + } + + #[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afe1, afe2])] + fn idle(mut c: idle::Context) -> ! { + + let mut socket_set_entries: [_; 8] = Default::default(); + let mut sockets = net::socket::SocketSet::new(&mut socket_set_entries[..]); + + let mut rx_storage = [0; TCP_RX_BUFFER_SIZE]; + let mut tx_storage = [0; TCP_TX_BUFFER_SIZE]; + let tcp_handle = { + let tcp_rx_buffer = net::socket::TcpSocketBuffer::new(&mut rx_storage[..]); + let tcp_tx_buffer = net::socket::TcpSocketBuffer::new(&mut tx_storage[..]); + let tcp_socket = net::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + sockets.add(tcp_socket) + }; + + let mut server = server::Server::new(); + + let mut time = 0u32; + let mut next_ms = Instant::now(); + + // TODO: Replace with reference to CPU clock from CCDR. + next_ms += 400_000.cycles(); + + loop { + let tick = Instant::now() > next_ms; + + if tick { + next_ms += 400_000.cycles(); + time += 1; + } + + { + let socket = + &mut *sockets.get::(tcp_handle); + if socket.state() == net::socket::TcpState::CloseWait { + socket.close(); + } else if !(socket.is_open() || socket.is_listening()) { + socket + .listen(1235) + .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); + } else { + server.poll(socket, |req| { + info!("Got request: {:?}", req); + route_request!(req, + readable_attributes: [ + "stabilizer/iir/state": (|| { + let state = c.resources.iir_state.lock(|iir_state| + server::Status { + t: time, + x0: iir_state[0][0], + y0: iir_state[0][2], + x1: iir_state[1][0], + y1: iir_state[1][2], + }); + + Ok::(state) + }), + "stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()), + "stabilizer/afe2/gain": (|| c.resources.afe2.get_gain()), + "pounder/in0": (|| { + c.resources.pounder.get_input_channel_state( + pounder::Channel::In0) + }), + "pounder/in1": (|| { + c.resources.pounder.get_input_channel_state( + pounder::Channel::In1) + }), + "pounder/out0": (|| { + c.resources.pounder.get_output_channel_state( + pounder::Channel::Out0) + }), + "pounder/out1": (|| { + c.resources.pounder.get_output_channel_state( + pounder::Channel::Out1) + }), + "pounder/dds/clock": (|| { + c.resources.pounder.get_dds_clock_config() + }) + ], + + modifiable_attributes: [ + "stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize] = req.iir; + + Ok::(req) + }) + }), + "stabilizer/iir2/state": server::IirRequest, (|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize] = req.iir; + + Ok::(req) + }) + }), + "pounder/in0": pounder::ChannelState, (|state| { + c.resources.pounder.set_channel_state(pounder::Channel::In0, + state) + }), + "pounder/in1": pounder::ChannelState, (|state| { + c.resources.pounder.set_channel_state(pounder::Channel::In1, + state) + }), + "pounder/out0": pounder::ChannelState, (|state| { + c.resources.pounder.set_channel_state(pounder::Channel::Out0, + state) + }), + "pounder/out1": pounder::ChannelState, (|state| { + c.resources.pounder.set_channel_state(pounder::Channel::Out1, + state) + }), + "pounder/dds/clock": pounder::DdsClockConfig, (|config| { + c.resources.pounder.configure_dds_clock(config) + }), + "stabilizer/afe1/gain": afe::Gain, (|gain| { + Ok::<(), ()>(c.resources.afe1.set_gain(gain)) + }), + "stabilizer/afe2/gain": afe::Gain, (|gain| { + Ok::<(), ()>(c.resources.afe2.set_gain(gain)) + }) + ] + ) + }); + } + } + + let sleep = match c.resources.net_interface.poll(&mut sockets, + net::time::Instant::from_millis(time as i64)) { + Ok(changed) => changed == false, + Err(net::Error::Unrecognized) => true, + Err(e) => { + info!("iface poll error: {:?}", e); + true + } + }; + + if sleep { + cortex_m::asm::wfi(); + } + } + } + + #[task(binds = ETH, priority = 1)] + fn eth(_: eth::Context) { + unsafe { ethernet::interrupt_handler() } + } + + extern "C" { + // hw interrupt handlers for RTFM to use for scheduling tasks + // one per priority + fn DCMI(); + fn JPEG(); + fn SDMMC(); + } +}; + +#[exception] +fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[exception] +fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} diff --git a/stabilizer/src/pounder/attenuators.rs b/stabilizer/src/pounder/attenuators.rs new file mode 100644 index 0000000..2042f47 --- /dev/null +++ b/stabilizer/src/pounder/attenuators.rs @@ -0,0 +1,77 @@ +use super::{Channel, Error}; + +/// Provide an interface for managing digital attenuators on Pounder hardware. +/// +/// Note: The digital attenuators do not allow read-back of attenuation. To circumvent this, this +/// driver maintains the attenuation code in both the shift register as well as the latched output +/// register of the attenuators. This allows the "active" attenuation code to be read back by +/// reading the shfit register. The downside of this approach is that any read is destructive, so a +/// read-writeback approach is employed. +pub trait AttenuatorInterface { + + /// Set the attenuation of a single channel. + /// + /// Args: + /// * `channel` - The pounder channel to configure the attenuation of. + /// * `attenuation` - The desired attenuation of the channel in dB. This has a resolution of + /// 0.5dB. + fn set_attenuation(&mut self, channel: Channel, attenuation: f32) -> Result { + if attenuation > 31.5 || attenuation < 0.0 { + return Err(Error::Bounds); + } + + // Calculate the attenuation code to program into the attenuator. The attenuator uses a + // code where the LSB is 0.5 dB. + let attenuation_code = (attenuation * 2.0) as u8; + + // Read all the channels, modify the channel of interest, and write all the channels back. + // This ensures the staging register and the output register are always in sync. + let mut channels = [0_u8; 4]; + self.read_all_attenuators(&mut channels)?; + + // The lowest 2 bits of the 8-bit shift register on the attenuator are ignored. Shift the + // attenuator code into the upper 6 bits of the register value. Note that the attenuator + // treats inputs as active-low, so the code is inverted before writing. + channels[channel as usize] = (!attenuation_code) << 2; + self.write_all_attenuators(&channels)?; + + // Finally, latch the output of the updated channel to force it into an active state. + self.latch_attenuators(channel)?; + + Ok(attenuation_code as f32 / 2.0) + } + + /// Get the attenuation of a channel. + /// + /// Args: + /// * `channel` - The channel to get the attenuation of. + /// + /// Returns: + /// The programmed attenuation of the channel in dB. + fn get_attenuation(&mut self, channel: Channel) -> Result { + let mut channels = [0_u8; 4]; + + // Reading the data always shifts data out of the staging registers, so we perform a + // duplicate write-back to ensure the staging register is always equal to the output + // register. + self.read_all_attenuators(&mut channels)?; + self.write_all_attenuators(&channels)?; + + // The attenuation code is stored in the upper 6 bits of the register, where each LSB + // represents 0.5 dB. The attenuator stores the code as active-low, so inverting the result + // (before the shift) has the affect of transforming the bits of interest (and the + // dont-care bits) into an active-high state and then masking off the don't care bits. If + // the shift occurs before the inversion, the upper 2 bits (which would then be don't + // care) would contain erroneous data. + let attenuation_code = (!channels[channel as usize]) >> 2; + + // Convert the desired channel code into dB of attenuation. + Ok(attenuation_code as f32 / 2.0) + } + + fn reset_attenuators(&mut self) -> Result<(), Error>; + + fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error>; + fn read_all_attenuators(&mut self, channels: &mut [u8; 4]) -> Result<(), Error>; + fn write_all_attenuators(&mut self, channels: &[u8; 4]) -> Result<(), Error>; +} diff --git a/stabilizer/src/pounder/mod.rs b/stabilizer/src/pounder/mod.rs new file mode 100644 index 0000000..7be3304 --- /dev/null +++ b/stabilizer/src/pounder/mod.rs @@ -0,0 +1,499 @@ +use mcp23017; +use ad9959; + +use serde::{Serialize, Deserialize}; + +mod attenuators; +mod rf_power; + +use super::hal; + +use attenuators::AttenuatorInterface; +use rf_power::PowerMeasurementInterface; + +use embedded_hal::{ + blocking::spi::Transfer, + adc::OneShot, +}; + +const EXT_CLK_SEL_PIN: u8 = 8 + 7; +#[allow(dead_code)] +const OSC_EN_N_PIN: u8 = 8 + 6; +const ATT_RST_N_PIN: u8 = 8 + 5; +const ATT_LE3_PIN: u8 = 8 + 3; +const ATT_LE2_PIN: u8 = 8 + 2; +const ATT_LE1_PIN: u8 = 8 + 1; +const ATT_LE0_PIN: u8 = 8 + 0; + +#[derive(Debug, Copy, Clone)] +pub enum Error { + Spi, + I2c, + Dds, + Qspi, + Bounds, + InvalidAddress, + InvalidChannel, + Adc, +} + +#[derive(Debug, Copy, Clone)] +pub enum Channel { + In0, + In1, + Out0, + Out1, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct DdsChannelState { + pub phase_offset: f32, + pub frequency: f64, + pub amplitude: f32, + pub enabled: bool, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct ChannelState { + pub parameters: DdsChannelState, + pub attenuation: f32, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct InputChannelState { + pub attenuation: f32, + pub power: f32, + pub mixer: DdsChannelState, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct OutputChannelState { + pub attenuation: f32, + pub channel: DdsChannelState, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct DdsClockConfig { + pub multiplier: u8, + pub reference_clock: f32, + pub external_clock: bool, +} + +impl Into for Channel { + /// Translate pounder channels to DDS output channels. + fn into(self) -> ad9959::Channel { + match self { + Channel::In0 => ad9959::Channel::Two, + Channel::In1 => ad9959::Channel::Four, + Channel::Out0 => ad9959::Channel::One, + Channel::Out1 => ad9959::Channel::Three, + } + } +} + +/// A structure for the QSPI interface for the DDS. +pub struct QspiInterface { + pub qspi: hal::qspi::Qspi, + mode: ad9959::Mode, +} + +impl QspiInterface { + /// Initialize the QSPI interface. + /// + /// Args: + /// * `qspi` - The QSPI peripheral driver. + pub fn new(mut qspi: hal::qspi::Qspi) -> Result { + // This driver only supports operation in 4-bit mode due to bus inconsistencies between the + // QSPI peripheral and the DDS. Instead, we will bit-bang communications in + // single-bit-two-wire to the DDS to configure it to 4-bit operation. + qspi.configure_mode(hal::qspi::QspiMode::FourBit).map_err(|_| Error::Qspi)?; + Ok(Self { qspi: qspi, mode: ad9959::Mode::SingleBitTwoWire }) + } +} + +impl ad9959::Interface for QspiInterface { + type Error = Error; + + /// Configure the operations mode of the interface. + /// + /// Args: + /// * `mode` - The newly desired operational mode. + fn configure_mode(&mut self, mode: ad9959::Mode) -> Result<(), Error> { + self.mode = mode; + + Ok(()) + } + + /// Write data over QSPI to the DDS. + /// + /// Args: + /// * `addr` - The address to write over QSPI to the DDS. + /// * `data` - The data to write. + fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Error> { + if (addr & 0x80) != 0 { + return Err(Error::InvalidAddress); + } + + // The QSPI interface implementation always operates in 4-bit mode because the AD9959 uses + // IO3 as SYNC_IO in some output modes. In order for writes to be successful, SYNC_IO must + // be driven low. However, the QSPI peripheral forces IO3 high when operating in 1 or 2 bit + // modes. As a result, any writes while in single- or dual-bit modes has to instead write + // the data encoded into 4-bit QSPI data so that IO3 can be driven low. + match self.mode { + ad9959::Mode::SingleBitTwoWire => { + // Encode the data into a 4-bit QSPI pattern. + + // In 4-bit mode, we can send 2 bits of address and data per byte transfer. As + // such, we need at least 4x more bytes than the length of data. To avoid dynamic + // allocation, we assume the maximum transaction length for single-bit-two-wire is + // 2 bytes. + let mut encoded_data: [u8; 12] = [0; 12]; + + if (data.len() * 4) > (encoded_data.len() - 4) { + return Err(Error::Bounds); + } + + // Encode the address into the first 4 bytes. + for address_bit in 0..8 { + let offset: u8 = { + if address_bit % 2 != 0 { + 4 + } else { + 0 + } + }; + + // Encode MSB first. Least significant bits are placed at the most significant + // byte. + let byte_position = 3 - (address_bit >> 1) as usize; + + if addr & (1 << address_bit) != 0 { + encoded_data[byte_position] |= 1 << offset; + } + } + + // Encode the data into the remaining bytes. + for byte_index in 0..data.len() { + let byte = data[byte_index]; + for bit in 0..8 { + let offset: u8 = { + if bit % 2 != 0 { + 4 + } else { + 0 + } + }; + + // Encode MSB first. Least significant bits are placed at the most + // significant byte. + let byte_position = 3 - (bit >> 1) as usize; + + if byte & (1 << bit) != 0 { + encoded_data[(byte_index + 1) * 4 + byte_position] |= 1 << offset; + } + } + } + + let (encoded_address, encoded_payload) = { + let end_index = (1 + data.len()) * 4; + (encoded_data[0], &encoded_data[1..end_index]) + }; + + self.qspi.write(encoded_address, &encoded_payload).map_err(|_| Error::Qspi) + }, + ad9959::Mode::FourBitSerial => { + self.qspi.write(addr, &data).map_err(|_| Error::Qspi) + }, + _ => { + Err(Error::Qspi) + } + } + } + + fn read(&mut self, addr: u8, mut dest: &mut [u8]) -> Result<(), Error> { + if (addr & 0x80) != 0 { + return Err(Error::InvalidAddress); + } + + // This implementation only supports operation (read) in four-bit-serial mode. + if self.mode != ad9959::Mode::FourBitSerial { + return Err(Error::Qspi); + } + + self.qspi.read(0x80_u8 | addr, &mut dest).map_err(|_| Error::Qspi) + } +} + +/// A structure containing implementation for Pounder hardware. +pub struct PounderDevices { + pub ad9959: ad9959::Ad9959>>, + mcp23017: mcp23017::MCP23017>, + attenuator_spi: hal::spi::Spi, + adc1: hal::adc::Adc, + adc2: hal::adc::Adc, + adc1_in_p: hal::gpio::gpiof::PF11, + adc2_in_p: hal::gpio::gpiof::PF14, +} + +impl PounderDevices +where + DELAY: embedded_hal::blocking::delay::DelayMs, +{ + /// Construct and initialize pounder-specific hardware. + /// + /// Args: + /// * `ad9959` - The DDS driver for the pounder hardware. + /// * `attenuator_spi` - A SPI interface to control digital attenuators. + /// * `adc1` - The ADC1 peripheral for measuring power. + /// * `adc2` - The ADC2 peripheral for measuring power. + /// * `adc1_in_p` - The input channel for the RF power measurement on IN0. + /// * `adc2_in_p` - The input channel for the RF power measurement on IN1. + pub fn new(mcp23017: mcp23017::MCP23017>, + ad9959: ad9959::Ad9959>>, + attenuator_spi: hal::spi::Spi, + adc1: hal::adc::Adc, + adc2: hal::adc::Adc, + adc1_in_p: hal::gpio::gpiof::PF11, + adc2_in_p: hal::gpio::gpiof::PF14, + ) -> Result { + let mut devices = Self { + mcp23017, + ad9959, + attenuator_spi, + adc1, + adc2, + adc1_in_p, + adc2_in_p, + }; + + // Configure power-on-default state for pounder. All LEDs are on, on-board oscillator + // selected, attenuators out of reset. Note that testing indicates the output state needs to + // be set first to properly update the output registers. + devices.mcp23017.all_pin_mode(mcp23017::PinMode::OUTPUT).map_err(|_| Error::I2c)?; + devices.mcp23017.write_gpio(mcp23017::Port::GPIOA, 0x3F).map_err(|_| Error::I2c)?; + devices.mcp23017.write_gpio(mcp23017::Port::GPIOB, 1 << 5).map_err(|_| Error::I2c)?; + + // Select the on-board clock with a 4x prescaler (400MHz). + devices.select_onboard_clock(4u8)?; + + Ok(devices) + } + + /// Select the an external for the DDS reference clock source. + /// + /// Args: + /// * `frequency` - The frequency of the external clock source. + /// * `multiplier` - The multiplier of the reference clock to use in the DDS. + fn select_external_clock(&mut self, frequency: f32, prescaler: u8) -> Result<(), Error>{ + self.mcp23017.digital_write(EXT_CLK_SEL_PIN, true).map_err(|_| Error::I2c)?; + self.ad9959.configure_system_clock(frequency, prescaler).map_err(|_| Error::Dds)?; + + Ok(()) + } + + /// Select the onboard oscillator for the DDS reference clock source. + /// + /// Args: + /// * `multiplier` - The multiplier of the reference clock to use in the DDS. + fn select_onboard_clock(&mut self, multiplier: u8) -> Result<(), Error> { + self.mcp23017.digital_write(EXT_CLK_SEL_PIN, false).map_err(|_| Error::I2c)?; + self.ad9959.configure_system_clock(100_000_000f32, multiplier).map_err(|_| Error::Dds)?; + + Ok(()) + } + + /// Configure the Pounder DDS clock. + /// + /// Args: + /// * `config` - The configuration of the DDS clock desired. + pub fn configure_dds_clock(&mut self, config: DdsClockConfig) -> Result<(), Error> { + if config.external_clock { + self.select_external_clock(config.reference_clock, config.multiplier) + } else { + self.select_onboard_clock(config.multiplier) + } + } + + /// Get the pounder DDS clock configuration + /// + /// Returns: + /// The current pounder DDS clock configuration. + pub fn get_dds_clock_config(&mut self) -> Result { + let external_clock = self.mcp23017.digital_read(EXT_CLK_SEL_PIN).map_err(|_| Error::I2c)?; + let multiplier = self.ad9959.get_reference_clock_multiplier().map_err(|_| Error::Dds)?; + let reference_clock = self.ad9959.get_reference_clock_frequency(); + + Ok(DdsClockConfig{multiplier, reference_clock, external_clock}) + } + + /// Get the state of a Pounder input channel. + /// + /// Args: + /// * `channel` - The pounder channel to get the state of. Must be an input channel + /// + /// Returns: + /// The read-back channel input state. + pub fn get_input_channel_state(&mut self, channel: Channel) -> Result { + match channel { + Channel::In0 | Channel::In1 => { + let channel_state = self.get_dds_channel_state(channel)?; + + let attenuation = self.get_attenuation(channel)?; + let power = self.measure_power(channel)?; + + Ok(InputChannelState { + attenuation: attenuation, + power: power, + mixer: channel_state + }) + } + _ => Err(Error::InvalidChannel), + } + } + + /// Get the state of a DDS channel. + /// + /// Args: + /// * `channel` - The pounder channel to get the state of. + /// + /// Returns: + /// The read-back channel state. + fn get_dds_channel_state(&mut self, channel: Channel) -> Result { + let frequency = self.ad9959.get_frequency(channel.into()).map_err(|_| Error::Dds)?; + let phase_offset = self.ad9959.get_phase(channel.into()).map_err(|_| Error::Dds)?; + let amplitude = self.ad9959.get_amplitude(channel.into()).map_err(|_| Error::Dds)?; + let enabled = self.ad9959.is_enabled(channel.into()).map_err(|_| Error::Dds)?; + + Ok(DdsChannelState {phase_offset, frequency, amplitude, enabled}) + } + + /// Get the state of a DDS output channel. + /// + /// Args: + /// * `channel` - The pounder channel to get the output state of. Must be an output channel. + /// + /// Returns: + /// The read-back output channel state. + pub fn get_output_channel_state(&mut self, channel: Channel) -> Result { + match channel { + Channel::Out0 | Channel::Out1 => { + let channel_state = self.get_dds_channel_state(channel)?; + let attenuation = self.get_attenuation(channel)?; + + Ok(OutputChannelState { + attenuation: attenuation, + channel: channel_state, + }) + } + _ => Err(Error::InvalidChannel), + } + } + + /// Configure a DDS channel. + /// + /// Args: + /// * `channel` - The pounder channel to configure. + /// * `state` - The state to configure the channel for. + pub fn set_channel_state(&mut self, channel: Channel, state: ChannelState) -> Result<(), Error> { + self.ad9959.set_frequency(channel.into(), state.parameters.frequency).map_err(|_| Error::Dds)?; + self.ad9959.set_phase(channel.into(), state.parameters.phase_offset).map_err(|_| Error::Dds)?; + self.ad9959.set_amplitude(channel.into(), state.parameters.amplitude).map_err(|_| Error::Dds)?; + + if state.parameters.enabled { + self.ad9959.enable_channel(channel.into()).map_err(|_| Error::Dds)?; + } else { + self.ad9959.disable_channel(channel.into()).map_err(|_| Error::Dds)?; + } + + self.set_attenuation(channel, state.attenuation)?; + + Ok(()) + } +} + +impl AttenuatorInterface for PounderDevices +{ + /// Reset all of the attenuators to a power-on default state. + fn reset_attenuators(&mut self) -> Result<(), Error> { + self.mcp23017.digital_write(ATT_RST_N_PIN, false).map_err(|_| Error::I2c)?; + // TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is + // sufficient. Document the delay here. + self.mcp23017.digital_write(ATT_RST_N_PIN, true).map_err(|_| Error::I2c)?; + + Ok(()) + } + + /// Latch a configuration into a digital attenuator. + /// + /// Args: + /// * `channel` - The attenuator channel to latch. + fn latch_attenuators(&mut self, channel: Channel) -> Result<(), Error> { + let pin = match channel { + Channel::In0 => ATT_LE0_PIN, + Channel::In1 => ATT_LE2_PIN, + Channel::Out0 => ATT_LE1_PIN, + Channel::Out1 => ATT_LE3_PIN, + }; + + self.mcp23017.digital_write(pin, true).map_err(|_| Error::I2c)?; + // TODO: Measure the I2C transaction speed to the RST pin to ensure that the delay is + // sufficient. Document the delay here. + self.mcp23017.digital_write(pin, false).map_err(|_| Error::I2c)?; + + Ok(()) + } + + /// Read the raw attenuation codes stored in the attenuator shift registers. + /// + /// Args: + /// * `channels` - A slice to store the channel readings into. + fn read_all_attenuators(&mut self, channels: &mut [u8; 4]) -> Result<(), Error> { + self.attenuator_spi.transfer(channels).map_err(|_| Error::Spi)?; + + Ok(()) + } + + /// Write the attenuator shift registers. + /// + /// Args: + /// * `channels` - The data to write into the attenuators. + fn write_all_attenuators(&mut self, channels: &[u8; 4]) -> Result<(), Error> { + let mut result = [0_u8; 4]; + result.clone_from_slice(channels); + self.attenuator_spi.transfer(&mut result).map_err(|_| Error::Spi)?; + + Ok(()) + } +} + +impl PowerMeasurementInterface for PounderDevices { + /// Sample an ADC channel. + /// + /// Args: + /// * `channel` - The channel to sample. + /// + /// Returns: + /// The sampled voltage of the specified channel. + fn sample_converter(&mut self, channel: Channel) -> Result { + let adc_scale = match channel { + Channel::In0 => { + let adc_reading: u32 = self.adc1.read(&mut self.adc1_in_p).map_err(|_| Error::Adc)?; + adc_reading as f32 / self.adc1.max_sample() as f32 + }, + Channel::In1 => { + let adc_reading: u32 = self.adc2.read(&mut self.adc2_in_p).map_err(|_| Error::Adc)?; + adc_reading as f32 / self.adc2.max_sample() as f32 + }, + _ => return Err(Error::InvalidChannel), + }; + + // Convert analog percentage to voltage. Note that the ADC uses an external 2.048V analog + // reference. + Ok(adc_scale * 2.048) + } +} diff --git a/stabilizer/src/pounder/rf_power.rs b/stabilizer/src/pounder/rf_power.rs new file mode 100644 index 0000000..57a7f7d --- /dev/null +++ b/stabilizer/src/pounder/rf_power.rs @@ -0,0 +1,20 @@ +use super::{Error, Channel}; + +/// Provide an interface to measure RF input power in dB. +pub trait PowerMeasurementInterface { + fn sample_converter(&mut self, channel: Channel) -> Result; + + /// Measure the power of an input channel in dBm. + /// + /// Note: This function assumes the input channel is connected to an AD8363 output. + /// + /// Args: + /// * `channel` - The pounder channel to measure the power of in dBm. + fn measure_power(&mut self, channel: Channel) -> Result { + let analog_measurement = self.sample_converter(channel)?; + + // The AD8363 with VSET connected to VOUT provides an output voltage of 51.7mV / dB at + // 100MHz. It also indicates a y-intercept of -58dBm. + Ok(analog_measurement / 0.0517 - 58.0) + } +} diff --git a/stabilizer/src/server.rs b/stabilizer/src/server.rs new file mode 100644 index 0000000..7361592 --- /dev/null +++ b/stabilizer/src/server.rs @@ -0,0 +1,225 @@ +use heapless::{ + consts::*, + String, + Vec +}; + +use core::fmt::Write; + + +use serde::{ + Deserialize, + Serialize +}; + +use serde_json_core::{ + de::from_slice, + ser::to_string +}; + +use super::net; +use super::iir; + +#[derive(Deserialize, Serialize, Debug)] +pub enum AccessRequest { + Read, + Write, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Request<'a> { + pub req: AccessRequest, + pub attribute: &'a str, + pub value: String, +} + +#[derive(Serialize, Deserialize)] +pub struct IirRequest { + pub channel: u8, + pub iir: iir::IIR, +} + +#[derive(Serialize)] +pub struct Response { + code: i32, + attribute: String, + value: String, +} + +impl<'a> Request<'a> { + pub fn restore_value(&mut self) { + let mut new_value: String = String::new(); + for byte in self.value.as_str().chars() { + if byte == '\'' { + new_value.push('"').unwrap(); + } else { + new_value.push(byte).unwrap(); + } + } + + self.value = new_value; + } +} + +impl Response { + + /// Remove all double quotation marks from the `value` field of a response. + fn sanitize_value(&mut self) { + let mut new_value: String = String::new(); + for byte in self.value.as_str().chars() { + if byte == '"' { + new_value.push('\'').unwrap(); + } else { + new_value.push(byte).unwrap(); + } + } + + self.value = new_value; + } + + /// Remove all double quotation marks from the `value` field of a response and wrap it in single + /// quotes. + fn wrap_and_sanitize_value(&mut self) { + let mut new_value: String = String::new(); + new_value.push('\'').unwrap(); + for byte in self.value.as_str().chars() { + if byte == '"' { + new_value.push('\'').unwrap(); + } else { + new_value.push(byte).unwrap(); + } + } + new_value.push('\'').unwrap(); + + self.value = new_value; + } + + /// Construct a successful reply. + /// + /// Note: `value` will be sanitized to convert all single quotes to double quotes. + /// + /// Args: + /// * `attrbute` - The attribute of the success. + /// * `value` - The value of the attribute. + pub fn success<'a, 'b>(attribute: &'a str, value: &'b str) -> Self + { + let mut res = Self { code: 200, attribute: String::from(attribute), value: String::from(value)}; + res.sanitize_value(); + res + } + + /// Construct an error reply. + /// + /// Note: `message` will be sanitized to convert all single quotes to double quotes. + /// + /// Args: + /// * `attrbute` - The attribute of the success. + /// * `message` - The message denoting the error. + pub fn error<'a, 'b>(attribute: &'a str, message: &'b str) -> Self + { + let mut res = Self { code: 400, attribute: String::from(attribute), value: String::from(message)}; + res.wrap_and_sanitize_value(); + res + } + + /// Construct a custom reply. + /// + /// Note: `message` will be sanitized to convert all single quotes to double quotes. + /// + /// Args: + /// * `attrbute` - The attribute of the success. + /// * `message` - The message denoting the status. + pub fn custom<'a>(code: i32, message : &'a str) -> Self + { + let mut res = Self { code: code, attribute: String::from(""), value: String::from(message)}; + res.wrap_and_sanitize_value(); + res + } +} + +#[derive(Serialize)] +pub struct Status { + pub t: u32, + pub x0: f32, + pub y0: f32, + pub x1: f32, + pub y1: f32, +} + +pub fn json_reply(socket: &mut net::socket::TcpSocket, msg: &T) { + let mut u: String = to_string(msg).unwrap(); + u.push('\n').unwrap(); + socket.write_str(&u).unwrap(); +} + +pub struct Server { + data: Vec, + discard: bool, +} + +impl Server { + /// Construct a new server object for managing requests. + pub fn new() -> Self { + Self { + data: Vec::new(), + discard: false, + } + } + + /// Poll the server for potential data updates. + /// + /// Args: + /// * `socket` - The socket to check contents from. + /// * `f` - A closure that can be called if a request has been received on the server. + pub fn poll( + &mut self, + socket: &mut net::socket::TcpSocket, + mut f: F, + ) + where + F: FnMut(&Request) -> Response + { + while socket.can_recv() { + let found = socket.recv(|buf| { + let (len, found) = + match buf.iter().position(|&c| c as char == '\n') { + Some(end) => (end + 1, true), + None => (buf.len(), false), + }; + if self.data.len() + len >= self.data.capacity() { + self.discard = true; + self.data.clear(); + } else if !self.discard && len > 0 { + self.data.extend_from_slice(&buf[..len]).unwrap(); + } + (len, found) + }).unwrap(); + + if found { + if self.discard { + self.discard = false; + json_reply(socket, &Response::custom(520, "command buffer overflow")); + } else { + let r = from_slice::(&self.data[..self.data.len() - 1]); + match r { + Ok(mut res) => { + // Note that serde_json_core doesn't escape quotations within a string. + // To account for this, we manually translate all single quotes to + // double quotes. This occurs because we doubly-serialize this field in + // some cases. + res.restore_value(); + let response = f(&res); + json_reply(socket, &response); + }, + Err(err) => { + warn!("parse error {:?}", err); + json_reply(socket, &Response::custom(550, "parse error")); + } + } + } + self.data.clear(); + } + } + } +} + diff --git a/stm32h7xx-hal b/stm32h7xx-hal new file mode 160000 index 0000000..ff00e93 --- /dev/null +++ b/stm32h7xx-hal @@ -0,0 +1 @@ +Subproject commit ff00e938f2b226211c178f26c092f36462c44404