commit 9dba818a12b3bb3a33f1e9158c177f8ce0a44576 Author: Nathan Rashleigh Date: Sun Jun 14 19:48:39 2026 +1000 init diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..654e59b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,17 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip STM32F411VETx" + +rustflags = [ + # LLD (shipped with the Rust toolchain) is used as the default linker + "-C", "link-arg=-Tlink.x", + # defmt's linker script, must come after link.x + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +# defmt filters log levels at COMPILE time; without this, info!/debug! are +# stripped and only println! survives. "debug" enables debug/info/warn/error. +DEFMT_LOG = "debug" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db6e765 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +kttl.sublime-workspace diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8605f16 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,568 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version 0.4.1", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.0", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757110fd3748162dddbea88854529888a7e9e36929058118efe06f9f88d93b64" +dependencies = [ + "cortex-m", + "defmt 0.3.100", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "f411" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 0.3.100", + "defmt-rtt", + "l3gd20", + "libm", + "lsm303dlhc", + "panic-probe", + "stm32f4", + "stm32f4xx-hal", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "fugit-timer" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9607bfc4c388f9d629704f56ede4a007546cad417b3bcd6fc7c87dc7edce04a" +dependencies = [ + "fugit", + "nb 1.1.0", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f6ea017b297ec65b8a108c6e9ad6879460721fb3b6b23abf690970147fc28" +dependencies = [ + "typenum", +] + +[[package]] +name = "kttl" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 0.3.100", + "defmt-rtt", + "f411", + "libm", + "panic-probe", +] + +[[package]] +name = "l3gd20" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86113fcb2b542fbd5636a7057fd1f0c39ea38b516383b557114027d5bd0cf491" +dependencies = [ + "embedded-hal 0.2.7", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lsm303dlhc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5d1a5c290951321d1b0d4a40edd828537de9889134a0e67c5146542ae57706" +dependencies = [ + "cast", + "embedded-hal 0.2.7", + "generic-array", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "panic-probe" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" +dependencies = [ + "cortex-m", + "defmt 0.3.100", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[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", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stm32f4" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41bd8922df364cb7054cae71a95dcbb32cffd25b5c0f9c00272ef8c82279a65" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "portable-atomic", + "vcell", +] + +[[package]] +name = "stm32f4xx-hal" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b5b520616b1ebd18b3ac2d809b8ae47851b14efc63aea7c85d8a45c1daa6fbb" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "cortex-m-rt", + "document-features", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-nb", + "embedded-io", + "embedded-storage", + "enumflags2", + "fugit", + "fugit-timer", + "nb 1.1.0", + "rand_core 0.6.4", + "rand_core 0.9.5", + "stm32f4", + "time", + "void", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f197e3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "kttl" +version = "0.1.0" +edition = "2024" + +[dependencies] +# board support: re-exports the HAL as `f411::hal`, plus the L3gd20/Lsm303dlhc +# interfaces and the `led` module +f411 = { path = "../f411" } + +cortex-m = "0.7.7" +cortex-m-rt = "0.7.5" + +defmt = "0.3" +defmt-rtt = "0.3" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +# math for the thermistor Beta equation (ln) in no_std +libm = "0.2" + +[profile.release] +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size on Flash +lto = true # better optimizations diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ed87434 --- /dev/null +++ b/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script (memory.x) somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("etc/memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when memory.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=etc/memory.x"); +} diff --git a/etc/69-probe-rs.rules b/etc/69-probe-rs.rules new file mode 100644 index 0000000..5a5e178 --- /dev/null +++ b/etc/69-probe-rs.rules @@ -0,0 +1,151 @@ +# Copy this file to /etc/udev/rules.d/ +# If rules fail to reload automatically, you can refresh udev rules +# with the command "udevadm control --reload" + +# This rules are based on the udev rules from the OpenOCD project, with unsupported probes removed. +# See http://openocd.org/ for more details. +# +# This file is available under the GNU General Public License v2.0 + +ACTION!="add|change", GOTO="probe_rs_rules_end" + +SUBSYSTEM=="gpio", MODE="0660", GROUP="plugdev", TAG+="uaccess" + +SUBSYSTEM!="usb|tty|hidraw", GOTO="probe_rs_rules_end" + +# Please keep this list sorted by VID:PID + +# STMicroelectronics ST-LINK V1 +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3744", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# STMicroelectronics ST-LINK/V2 +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# STMicroelectronics ST-LINK/V2.1 +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3752", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# STMicroelectronics STLINK-V3 +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3753", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3754", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3757", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# SEGGER J-Link +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0101", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0102", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0103", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0104", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0105", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0107", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0108", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1001", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1002", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1003", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1004", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1005", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1006", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1007", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1008", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1009", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="100f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1010", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1011", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1012", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1013", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1014", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1016", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1017", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1018", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1019", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="101f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1020", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1021", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1022", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1023", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1024", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1025", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1026", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1027", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1028", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1029", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="102f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1050", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1051", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1052", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1053", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1054", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1055", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1056", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1057", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1058", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1059", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="105f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1060", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1061", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1062", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1063", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1064", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1065", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1066", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1067", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1068", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1069", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="106f", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# FT232H +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="660", GROUP="plugdev", TAG+="uaccess" +# FT2232x +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="660", GROUP="plugdev", TAG+="uaccess" +# FT4232H +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6011", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# FTDI-based Olimex devices +ATTRS{idVendor}=="0x15ba", ATTRS{idProduct}=="0x0003", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0x15ba", ATTRS{idProduct}=="0x0004", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0x15ba", ATTRS{idProduct}=="0x002a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0x15ba", ATTRS{idProduct}=="0x002b", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# Espressif USB JTAG/serial debug unit +ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="660", GROUP="plugdev", TAG+="uaccess" +# Espressif USB Bridge +ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1002", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# CMSIS-DAP compatible adapters +ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess" +# WCH Link (CMSIS-DAP compatible adapter) +ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="8010", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="8011", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# Infineon KitProg +ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="f155", MODE="660", GROUP="plugdev", TAG+="uaccess" + +LABEL="probe_rs_rules_end" diff --git a/etc/memory.x b/etc/memory.x new file mode 100644 index 0000000..123296d --- /dev/null +++ b/etc/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 512K + RAM : ORIGIN = 0x20000000, LENGTH = 128K +} diff --git a/kttl.sublime-project b/kttl.sublime-project new file mode 100644 index 0000000..9aa57a9 --- /dev/null +++ b/kttl.sublime-project @@ -0,0 +1,11 @@ +{ + "folders": + [ + { + "path": "." + }, + { + "path": "/home/ntr/code/f411" + } + ] +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..acd59d6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,198 @@ +//! Kettle controller: press the switch to boil to a setpoint, then brew-timer. +//! +//! The button behaves like a kettle's on-switch: pressing it engages the heater +//! (the state latches in firmware, like the switch staying down), and the +//! heater runs until the water reaches the setpoint, at which point it switches +//! off ("pops up") and a brew timer starts. +//! +//! Sensor — NTC divider on the 3V rail (NOT 5V; ADC ref is VDDA ~= 3.0 V): +//! +//! 3V ── NTC ──┬── PC1 (P1 pin 7, ADC1_IN11) +//! ├── R_FIXED ── GND +//! └── 100nF ──── GND (optional noise filter) +//! R_ntc = R_FIXED * (ADC_MAX - raw) / raw (ratiometric), then Beta -> degC +//! +//! On-switch — the board's B1 USER button (PA0, active-high). It is polled +//! every POLL_MS (not just once per control tick), so a quick tap registers. +//! +//! Outputs (LEDs via their own ~220-330R resistor, anode to pin, cathode GND): +//! +//! PC5 (P1 pin 19) HEATER -> relay/SSR (high = heat on) +//! PC2 (P1 pin 10) ──[R]──▶|──GND RED = heater on +//! PC4 (P1 pin 20) ──[R]──▶|──GND BLUE = at/above setpoint +//! on-board user LEDs (PD12-15) — brew timer: north LED (orange) = counting + +#![no_std] +#![no_main] + +// defmt global logger (RTT transport) + panic handler +use defmt_rtt as _; +use panic_probe as _; + +use cortex_m_rt::entry; +// the HAL is re-exported by the f411 board-support crate as `f411::hal` +use f411::{ + hal::{ + adc::{ + config::{AdcConfig, SampleTime}, + Adc, + }, + pac, + prelude::*, + rcc::Config, + }, + led::LedCompass, +}; + +// ---- NTC parameters: edit to match your thermistor ---- +const R_FIXED: f32 = 10_000.0; // fixed divider resistor (ohms) +const R0: f32 = 10_000.0; // NTC nominal resistance at T0 (ohms) +const BETA: f32 = 3950.0; // NTC beta coefficient (kelvin) +const T0: f32 = 298.15; // reference temperature for R0 (kelvin, = 25 C) +// 12-bit ADC full scale (AdcConfig::default() is Resolution::Twelve) +const ADC_MAX: f32 = 4095.0; + +// ---- Control parameters ---- +// Target temperature. Low (~25 C) so it can be exercised by warming the +// thermistor by hand; raise toward ~90-95 C for an actual kettle. +const SETPOINT_C: f32 = 25.0; + +// ---- Timing ---- +// Control cadence: temperature is read and the timer advances once per tick. +const TICK_MS: u32 = 1000; +// Button poll interval within a tick — short enough to catch a normal tap. +const POLL_MS: u32 = 20; +// brew duration, in ticks (= seconds at TICK_MS = 1000). Short for the bench; +// set to your brew/steep time (e.g. 180 for 3 minutes). +const BREW_TICKS: u32 = 10; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let p = pac::Peripherals::take().unwrap(); + + // 64 MHz sysclk -> PCLK2 64 MHz -> ADC clock 32 MHz (Pclk2_div_2), within + // the F411's 36 MHz ADC limit. + let mut rcc = p.RCC.freeze(Config::hsi().sysclk(64.MHz())); + + let gpioa = p.GPIOA.split(&mut rcc); + let button = gpioa.pa0.into_pull_down_input(); // B1 USER on-switch: high = pressed + + let gpioc = p.GPIOC.split(&mut rcc); + let therm = gpioc.pc1.into_analog(); + let mut heater = gpioc.pc5.into_push_pull_output(); // actuator (relay/SSR later) + let mut led_heating = gpioc.pc2.into_push_pull_output(); // red: heater on + let mut led_at_temp = gpioc.pc4.into_push_pull_output(); // blue: at/above setpoint + + // On-board user LEDs (PD12-15) used as the brew-timer indicator; only the + // north LED (LD3, orange) is driven. + let mut compass = LedCompass::new(p.GPIOD.split(&mut rcc)); + + heater.set_low(); + led_heating.set_low(); + led_at_temp.set_low(); + compass.n.off(); + compass.e.off(); + compass.s.off(); + compass.w.off(); + + let mut adc = Adc::new(p.ADC1, true, AdcConfig::default(), &mut rcc); + let mut delay = cp.SYST.delay(&rcc.clocks); + + defmt::info!("kettle: setpoint {} C, brew {} s", SETPOINT_C, BREW_TICKS); + + // ---- state (carried across ticks) ---- + let mut heating = false; // kettle switched on, heating toward setpoint + let mut countdown: Option = None; // brew-timer ticks remaining + let mut prev_pressed = button.is_high(); // for press edge detection + + loop { + let raw = adc.convert(&therm, SampleTime::Cycles_480); + + // Fail-safe: a disconnected thermistor reads raw 0; force the heater off + // rather than risk boiling dry, and freeze the rest of the state. + if raw == 0 { + heating = false; + heater.set_low(); + led_heating.set_low(); + defmt::warn!("raw=0 — thermistor disconnected? heater forced OFF"); + prev_pressed = button.is_high(); + delay.delay_ms(TICK_MS); + continue; + } + + let r_ntc = R_FIXED * (ADC_MAX - raw as f32) / raw as f32; + // Beta equation: 1/T = 1/T0 + (1/BETA) * ln(R_ntc / R0) + let inv_t = 1.0 / T0 + (1.0 / BETA) * libm::logf(r_ntc / R0); + let temp_c = 1.0 / inv_t - 273.15; + + // Reaching the setpoint switches the kettle off ("pops up") and starts + // the brew timer. + if heating && temp_c >= SETPOINT_C { + heating = false; + countdown = Some(BREW_TICKS); + defmt::info!("setpoint reached — switched OFF, brew {} s", BREW_TICKS); + } + + let heat_on = heating; + heater.set_state(heat_on.into()); + led_heating.set_state(heat_on.into()); + led_at_temp.set_state((temp_c >= SETPOINT_C).into()); + + // Advance the brew timer. + if let Some(n) = countdown { + let n = n - 1; + if n == 0 { + countdown = None; + defmt::info!("brew COMPLETE"); + } else { + countdown = Some(n); + defmt::info!("brew: {} s left", n); + } + } + + // brew-timer indicator: north LED (orange) lit while counting. + if countdown.is_some() { + compass.n.on(); + } else { + compass.n.off(); + } + + // defmt has no float-precision hint, so render the temperature to one + // decimal by hand via deci-degrees; heater state is shown as a word. + let t_dc = libm::roundf(temp_c * 10.0) as i32; // tenths of a degree C + defmt::info!( + "T={=i32}.{=u32} C set={} C heater {}", + t_dc / 10, + (t_dc % 10).unsigned_abs(), + SETPOINT_C, + if heat_on { "ON" } else { "off" }, + ); + + // Wait one control tick, polling the on-switch frequently so a quick tap + // isn't missed. A press engages the heater immediately and cancels any + // running brew timer — like pushing the kettle switch back down. + let mut waited = 0; + while waited < TICK_MS { + delay.delay_ms(POLL_MS); + waited += POLL_MS; + + let pressed = button.is_high(); + if pressed && !prev_pressed { + // Engage only if there is actually heating to do. Uses the most + // recent temperature reading (from the top of this tick). + if temp_c < SETPOINT_C { + heating = true; + countdown = None; + heater.set_high(); + led_heating.set_high(); + compass.n.off(); + defmt::info!("switched ON"); + } else { + defmt::info!("press ignored — already at/above setpoint"); + } + } + prev_pressed = pressed; + } + } +}