From 0ea0231f421455fb26149130b173977d319f4a53 Mon Sep 17 00:00:00 2001 From: daylily Date: Mon, 7 Apr 2025 19:57:23 -0400 Subject: [PATCH] firmware: add firmware --- .vscode/settings.json | 4 + firmware/.cargo/config.toml | 8 + firmware/.gitignore | 1 + firmware/Cargo.lock | 1057 ++++++++++++++++++++++++++++++++ firmware/Cargo.toml | 48 ++ firmware/build.rs | 5 + firmware/memory.x | 11 + firmware/src/app/descriptor.rs | 23 + firmware/src/app/mod.rs | 86 +++ firmware/src/device/epd.rs | 63 ++ firmware/src/device/mod.rs | 2 + firmware/src/device/usb_hid.rs | 120 ++++ firmware/src/init.rs | 35 ++ firmware/src/main.rs | 79 +++ firmware/src/util.rs | 20 + 15 files changed, 1562 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 firmware/.cargo/config.toml create mode 100644 firmware/.gitignore create mode 100644 firmware/Cargo.lock create mode 100644 firmware/Cargo.toml create mode 100644 firmware/build.rs create mode 100644 firmware/memory.x create mode 100644 firmware/src/app/descriptor.rs create mode 100644 firmware/src/app/mod.rs create mode 100644 firmware/src/device/epd.rs create mode 100644 firmware/src/device/mod.rs create mode 100644 firmware/src/device/usb_hid.rs create mode 100644 firmware/src/init.rs create mode 100644 firmware/src/main.rs create mode 100644 firmware/src/util.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f6800af --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.targets": "thumbv7em-none-eabihf" +} diff --git a/firmware/.cargo/config.toml b/firmware/.cargo/config.toml new file mode 100644 index 0000000..a377130 --- /dev/null +++ b/firmware/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32F411CEUx' + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1 @@ +/target diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock new file mode 100644 index 0000000..cbed053 --- /dev/null +++ b/firmware/Cargo.lock @@ -0,0 +1,1057 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aligned" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" +dependencies = [ + "as-slice", +] + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-device-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c051592f59fe68053524b4c4935249b806f72c1f544cfb7abe4f57c3be258e" +dependencies = [ + "aligned", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "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 2.0.98", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[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 = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cac3b8a5644a9e02b75085ebad3b6deafdbdbdec04bb25086523828aa4dfd1" +dependencies = [ + "critical-section", + "defmt 1.0.1", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fea5ef5bed4d3468dfd44f5c9fa4cda8f54c86d4fb4ae683eacf9d39e2ea12" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6" +dependencies = [ + "cortex-m", + "critical-section", + "defmt 0.3.100", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "embassy-hal-internal" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59" +dependencies = [ + "cortex-m", + "critical-section", + "defmt 0.3.100", + "num-traits", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-stm32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e0bb733acdddbc7097765a47ce80bde2385647cf1d8427331931e06cff9a87" +dependencies = [ + "aligned", + "bit_field", + "bitflags 2.9.0", + "block-device-driver", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 0.3.100", + "document-features", + "embassy-embedded-hal", + "embassy-futures", + "embassy-hal-internal", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-utils", + "embassy-usb-driver", + "embassy-usb-synopsys-otg", + "embedded-can", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "embedded-storage", + "embedded-storage-async", + "futures-util", + "nb 1.1.0", + "proc-macro2", + "quote", + "rand_core", + "sdio-host", + "static_assertions", + "stm32-fmc", + "stm32-metapac", + "vcell", + "volatile-register", +] + +[[package]] +name = "embassy-sync" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 0.3.100", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 0.3.100", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83" +dependencies = [ + "embassy-executor", + "heapless", +] + +[[package]] +name = "embassy-usb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "embassy-usb-synopsys-otg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e753b23799329780c7ac434264026d0422044d6649ed70a73441b14a6436d7" +dependencies = [ + "critical-section", + "defmt 0.3.100", + "embassy-sync", + "embassy-usb-driver", +] + +[[package]] +name = "embedded-can" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "embedded-graphics" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044" +dependencies = [ + "az", + "byteorder", +] + +[[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-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-bus" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513e0b3a8fb7d3013a8ae17a834283f170deaf7d0eeab0a7c1a36ad4dd356d22" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + +[[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" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "defmt 0.3.100", + "embedded-io", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "epd-waveshare" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5080903b18a41a89d1dfe07cd5d7f800165ed4dcdf0513b770f83c1e4750150c" +dependencies = [ + "bit_field", + "embedded-graphics-core", + "embedded-hal 1.0.0", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "inkclip-firmware" +version = "0.1.0" +dependencies = [ + "base64", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 1.0.1", + "defmt-rtt", + "embassy-executor", + "embassy-futures", + "embassy-stm32", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embedded-graphics", + "embedded-hal-bus", + "epd-waveshare", + "panic-probe", + "usbd-hid", +] + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + +[[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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[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 2.0.98", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +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 = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "sdio-host" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93c025f9cfe4c388c328ece47d11a54a823da3b5ad0370b22d95ad47137f85a" + +[[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-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stm32-fmc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f0639399e2307c2446c54d91d4f1596343a1e1d5cab605b9cce11d0ab3858c" +dependencies = [ + "embedded-hal 0.2.7", +] + +[[package]] +name = "stm32-metapac" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc520f60f6653a32479a95b9180b33908f0cbbdf106609465ee7dea98f4f5b37" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml new file mode 100644 index 0000000..586415c --- /dev/null +++ b/firmware/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "inkclip-firmware" +version = "0.1.0" +edition = "2024" + +[dependencies] +embassy-stm32 = { version = "0.2", features = [ + "defmt", + "stm32f411ce", + "memory-x", + "time-driver-any", + "exti", +] } +embassy-sync = { version = "0.6", features = ["defmt"] } +embassy-executor = { version = "0.7", features = [ + "nightly", + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "defmt", +] } +embassy-time = { version = "0.4", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-usb = { version = "0.4", features = ["defmt"] } +embassy-futures = { version = "0.1", features = ["defmt"] } + +defmt = "1.0" +defmt-rtt = "1.0" + +cortex-m = { version = "0.7", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7" +embedded-hal-bus = { version = "0.3", features = ["async"] } +panic-probe = { version = "1.0", features = ["print-defmt"] } +critical-section = "1.1" + +epd-waveshare = "0.6" +embedded-graphics = "0.8" +usbd-hid = "0.8" +base64 = { version = "0.22", default-features = false } + +[profile.release] +opt-level = "z" diff --git a/firmware/build.rs b/firmware/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/firmware/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/firmware/memory.x b/firmware/memory.x new file mode 100644 index 0000000..8c45f57 --- /dev/null +++ b/firmware/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 512K + RAM : ORIGIN = 0x20000000, LENGTH = 128K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); diff --git a/firmware/src/app/descriptor.rs b/firmware/src/app/descriptor.rs new file mode 100644 index 0000000..ff35bf7 --- /dev/null +++ b/firmware/src/app/descriptor.rs @@ -0,0 +1,23 @@ +use usbd_hid::descriptor::generator_prelude::*; + +// Watch out the terminology! +// - An "output" report is a report from the host to the device (as the device is the one doing the output); +// - An "input" report is a report from the device to the host (as it is presumably triggered by user input). +#[gen_hid_descriptor( + (usage_page = VENDOR_DEFINED_END, usage = 0x01, collection = APPLICATION) = { + (report_id = 0x01, usage = 0x02) = { + write_pattern = output; + }; + (report_id = 0x02, usage = 0x03) = { + serial_number_request = output; + }; + (report_id = 0x02, usage = 0x03) = { + serial_number_response = input; + }; + } +)] +pub struct HIDReports { + write_pattern: [u8; 5000], + serial_number_request: u8, + serial_number_response: [u8; 16], +} diff --git a/firmware/src/app/mod.rs b/firmware/src/app/mod.rs new file mode 100644 index 0000000..edc27e4 --- /dev/null +++ b/firmware/src/app/mod.rs @@ -0,0 +1,86 @@ +use defmt::*; + +use embedded_graphics::prelude::*; +use epd_waveshare::epd1in54_v2::Display1in54; +use epd_waveshare::prelude::*; + +use crate::device::epd::Epd; +use crate::device::usb_hid::Hid; +use crate::util::{uid_base64, uid_base64_bytes}; + +pub mod descriptor; + +pub const USB_READ_BUFFER_SIZE: usize = 8192; +pub const USB_WRITE_BUFFER_SIZE: usize = 32; + +const WRITE_PATTERN_REPORT_ID: u8 = 0x01; +const SERIAL_NUMBER_REPORT_ID: u8 = 0x02; + +fn color_at(byte: u8, ix: i32) -> Color { + defmt::debug_assert!(0 <= ix && ix < 8); + + if byte & (1u8 << ix) == 0 { + Color::White + } else { + Color::Black + } +} + +pub struct MainLoop<'a> { + pub hid: Hid<'a, USB_READ_BUFFER_SIZE, USB_WRITE_BUFFER_SIZE>, + pub epd: Epd, +} + +impl<'a> MainLoop<'a> { + fn draw_pattern(&mut self, buf: &[u8]) { + debug!("Updating display"); + + let mut buf_size = buf.len(); + + if buf_size < 5000 { + warn!("WRITE_PATTERN buffer too short (buf_size = {})", buf_size); + } else if buf_size > 5000 { + warn!( + "WRITE_PATTERN buffer too long; truncating (buf_size = {})", + buf_size + ); + buf_size = 5000; + } + + let mut display = Display1in54::default(); + display.set_rotation(DisplayRotation::Rotate180); + let width = display.size().width as i32; + + for (stride, &byte) in buf[..buf_size].iter().enumerate() { + for stroll in 0..8 { + let ix = stride as i32 * 8 + stroll; + let x = ix % width; + let y = ix / width; + display.set_pixel(Pixel(Point { x, y }, color_at(byte, stroll))); + } + } + + self.epd.update_display(&display); + } + + async fn send_serial_number(&mut self) { + debug!("Sending serial number {}", uid_base64()); + + self.hid + .write(SERIAL_NUMBER_REPORT_ID, uid_base64_bytes()) + .await; + } + + pub async fn run(&mut self) -> ! { + let mut read_buf = [0u8; USB_READ_BUFFER_SIZE]; + + loop { + let (id, buf) = self.hid.read(&mut read_buf).await; + match id { + WRITE_PATTERN_REPORT_ID => self.draw_pattern(buf), + SERIAL_NUMBER_REPORT_ID => self.send_serial_number().await, + _ => warn!("Unknown report ID: {}", buf[0]), + } + } + } +} diff --git a/firmware/src/device/epd.rs b/firmware/src/device/epd.rs new file mode 100644 index 0000000..e16a874 --- /dev/null +++ b/firmware/src/device/epd.rs @@ -0,0 +1,63 @@ +use embassy_stm32::gpio::{self, Level, Pull, Speed}; +use embassy_stm32::spi::{self, MosiPin, SckPin, Spi}; +use embassy_stm32::time::Hertz; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; + +use epd_waveshare::epd1in54::Display1in54; +use epd_waveshare::epd1in54_v2::Epd1in54; +use epd_waveshare::prelude::*; + +type EpdSpiDevice = + ExclusiveDevice, gpio::Output<'static>, Delay>; + +pub struct Epd { + spi_device: EpdSpiDevice, + epd: Epd1in54< + EpdSpiDevice, + gpio::Input<'static>, + gpio::Output<'static>, + gpio::Output<'static>, + Delay, + >, +} + +impl Epd { + pub fn new( + spi: SPI, + cs: impl gpio::Pin, + sck: impl SckPin, + mosi: impl MosiPin, + busy: impl gpio::Pin, + dc: impl gpio::Pin, + rst: impl gpio::Pin, + ) -> Epd { + let cs_pin = gpio::Output::new(cs, Level::High, Speed::VeryHigh); + let busy_pin = gpio::Input::new(busy, Pull::Down); + let dc_pin = gpio::Output::new(dc, Level::High, Speed::VeryHigh); + let rst_pin = gpio::Output::new(rst, Level::High, Speed::VeryHigh); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = Hertz(20_000_000); + let spi_bus = Spi::new_blocking_txonly(spi, sck, mosi, spi_config); + let mut spi_device = ExclusiveDevice::new(spi_bus, cs_pin, Delay {}).unwrap(); + + let epd = Epd1in54::new( + &mut spi_device, + busy_pin, + dc_pin, + rst_pin, + &mut Delay {}, + Some(0), + ) + .unwrap(); + + return Epd { spi_device, epd }; + } + + pub fn update_display(&mut self, display: &Display1in54) { + self.epd + .update_and_display_frame(&mut self.spi_device, display.buffer(), &mut Delay {}) + .unwrap_or(()) + } +} diff --git a/firmware/src/device/mod.rs b/firmware/src/device/mod.rs new file mode 100644 index 0000000..9b6ad26 --- /dev/null +++ b/firmware/src/device/mod.rs @@ -0,0 +1,2 @@ +pub mod epd; +pub mod usb_hid; diff --git a/firmware/src/device/usb_hid.rs b/firmware/src/device/usb_hid.rs new file mode 100644 index 0000000..31e2774 --- /dev/null +++ b/firmware/src/device/usb_hid.rs @@ -0,0 +1,120 @@ +use defmt::*; + +use embassy_stm32::peripherals::USB_OTG_FS; +use embassy_stm32::usb::{self, DmPin, DpPin}; +use embassy_usb::class::hid::{self, HidReader, HidReaderWriter, HidWriter}; +use embassy_usb::driver::EndpointError; + +use crate::init::Irqs; + +pub type UsbBuilder<'d> = embassy_usb::Builder<'d, usb::Driver<'d, USB_OTG_FS>>; + +pub fn get_usb_builder<'d>( + usb_peri: USB_OTG_FS, + dp: impl DpPin, + dm: impl DmPin, + ep_out_buffer: &'d mut [u8], + driver_config: usb::Config, + builder_config: embassy_usb::Config<'d>, + config_descriptor: &'d mut [u8], + bos_descriptor: &'d mut [u8], + msos_descriptor: &'d mut [u8], + control_buf: &'d mut [u8], +) -> UsbBuilder<'d> { + let driver = usb::Driver::new_fs(usb_peri, Irqs, dp, dm, ep_out_buffer, driver_config); + + return embassy_usb::Builder::new( + driver, + builder_config, + config_descriptor, + bos_descriptor, + msos_descriptor, + control_buf, + ); +} + +pub struct Hid<'d, const READ_N: usize, const WRITE_N: usize> { + reader: HidReader<'d, usb::Driver<'d, USB_OTG_FS>, READ_N>, + writer: HidWriter<'d, usb::Driver<'d, USB_OTG_FS>, WRITE_N>, +} + +impl<'d, const READ_N: usize, const WRITE_N: usize> Hid<'d, READ_N, WRITE_N> { + pub fn new( + usb_builder: &mut UsbBuilder<'d>, + report_descriptor: &'d [u8], + state: &'d mut hid::State<'d>, + ) -> Hid<'d, READ_N, WRITE_N> { + let hid_config = hid::Config { + report_descriptor, + request_handler: None, + poll_ms: 10, + max_packet_size: 64, + }; + + let (reader, writer) = HidReaderWriter::new(usb_builder, state, hid_config).split(); + return Hid { reader, writer }; + } + + async fn internal_read<'a, const USE_ID: bool>( + &mut self, + buf: &'a mut [u8; READ_N], + ) -> (u8, &'a [u8]) { + loop { + match self.reader.read(buf).await { + Ok(len) => { + if USE_ID { + if len < 1 { + warn!("Received no report ID") + } else { + return (buf[0], &buf[1..len]); + } + } else { + return (0, &buf[..len]); + } + } + Err(hid::ReadError::BufferOverflow) => { + warn!("Buffer overflow when reading from host") + } + Err(hid::ReadError::Disabled) => { + info!("Endpoint disabled"); + self.reader.ready().await + } + Err(hid::ReadError::Sync(_)) => defmt::unreachable!(), + } + } + } + + pub async fn read<'a>(&mut self, buf: &'a mut [u8; READ_N]) -> (u8, &'a [u8]) { + self.internal_read::(buf).await + } + + #[allow(dead_code)] + pub async fn read_no_id<'a>(&mut self, buf: &'a mut [u8; READ_N]) -> &'a [u8] { + self.internal_read::(buf).await.1 + } + + pub async fn write(&mut self, id: u8, report: &[u8]) { + let payload_size = report.len() + 1; + + if payload_size > WRITE_N { + error!( + "Buffer overflow when writing to host (report_size = {})", + payload_size + ); + return; + } + + let mut payload = [id; WRITE_N]; + payload[1..payload_size].copy_from_slice(report); + + self.writer + .write(&payload[..payload_size]) + .await + .unwrap_or_else(|err| match err { + EndpointError::BufferOverflow => error!("Buffer overflow when writing to host"), + EndpointError::Disabled => warn!("Endpoint disabled"), + }); + + info!("Written report #{}: {}", id, report); + } +} diff --git a/firmware/src/init.rs b/firmware/src/init.rs new file mode 100644 index 0000000..0035a5b --- /dev/null +++ b/firmware/src/init.rs @@ -0,0 +1,35 @@ +use embassy_stm32::bind_interrupts; +use embassy_stm32::peripherals::USB_OTG_FS; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb; +use embassy_stm32::{Config, Peripherals}; + +bind_interrupts!(pub struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +pub fn init_peripherals() -> Peripherals { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(12_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV12, // 12 / 12 = 1 MHz + mul: PllMul::MUL192, // 1 * 192 = 192 MHz + divp: Some(PllPDiv::DIV2), // 192 / 2 = 96 MHz <= 100 MHz max freq + divq: Some(PllQDiv::DIV4), // 192 / 4 = 48 MHz necessary for USB + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV4; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + + return embassy_stm32::init(config); +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs new file mode 100644 index 0000000..ce6faa3 --- /dev/null +++ b/firmware/src/main.rs @@ -0,0 +1,79 @@ +#![no_main] +#![no_std] +#![feature(impl_trait_in_assoc_type)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; + +use device::epd::Epd; +use device::usb_hid::Hid; +use device::usb_hid::get_usb_builder; +use embassy_stm32::usb; +use embassy_usb::UsbVersion; +use embassy_usb::class::hid; +use usbd_hid::descriptor::generator_prelude::*; + +use init::init_peripherals; +use util::uid_base64; + +use app::MainLoop; +use app::descriptor::HIDReports; + +use {defmt_rtt as _, panic_probe as _}; + +mod app; +mod device; +mod init; +mod util; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peri = init_peripherals(); + + info!("Initialized peripherals"); + + let epd = Epd::new( + peri.SPI1, peri.PA4, peri.PA5, peri.PA7, peri.PB10, peri.PB0, peri.PB1, + ); + + info!("Established SPI link to e-paper display"); + + let mut ep_out_buffer = [0u8; 256]; + let mut config_descriptor = [0; 256]; + let mut control_buf = [0; 256]; + let mut state = hid::State::new(); + + let mut builder_config = embassy_usb::Config::new(0x1209, 0xc9c9); + builder_config.manufacturer = Some("daylily"); + builder_config.product = Some("“Inkclip” 1.54″ ePaper Accessory"); + builder_config.serial_number = Some(uid_base64()); + + // Caution: past USB 2.0 (including USB 2.1 aka 2.0 LPM), BOS descriptors become necessary + builder_config.bcd_usb = UsbVersion::Two; + + let mut builder = get_usb_builder( + peri.USB_OTG_FS, + peri.PA12, + peri.PA11, + &mut ep_out_buffer, + usb::Config::default(), + builder_config, + &mut config_descriptor, + &mut [], // USB 2.0 does not require BOS descriptors + &mut [], // Non vendor-specific devices/interfaces do not require MS OS descriptors + &mut control_buf, + ); + + let usb_hid = Hid::new(&mut builder, HIDReports::desc(), &mut state); + + let mut main_loop = MainLoop { epd, hid: usb_hid }; + let main_fut = main_loop.run(); + + let mut usb_device = builder.build(); + let usb_fut = usb_device.run(); + + info!("Initialized USB HID device"); + + join(main_fut, usb_fut).await; +} diff --git a/firmware/src/util.rs b/firmware/src/util.rs new file mode 100644 index 0000000..76bf3e0 --- /dev/null +++ b/firmware/src/util.rs @@ -0,0 +1,20 @@ +use base64::prelude::*; +use embassy_stm32::uid::uid; + +pub fn uid_base64() -> &'static str { + unsafe { core::str::from_utf8_unchecked(uid_base64_bytes()) } +} + +#[allow(static_mut_refs)] +pub fn uid_base64_bytes() -> &'static [u8] { + static mut UID_BASE64: [u8; 16] = [0; 16]; + static mut LOADED: bool = false; + critical_section::with(|_| unsafe { + if !LOADED { + let uid = uid(); + BASE64_STANDARD.encode_slice(uid, &mut UID_BASE64).unwrap(); + LOADED = true; + } + }); + unsafe { &*core::ptr::addr_of!(UID_BASE64) } +}