diff --git a/.gitignore b/.gitignore index 84faace..62db18b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /target *.uf2 +abi_sys.h +assets/gif/bad_apple.gif +picolibc +user-apps/gboy/Peanut-GB diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..26531ce --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "picolibc"] + path = picolibc + url = https://github.com/picolibc/picolibc +[submodule "user-apps/gboy/Peanut-GB"] + path = user-apps/gboy/Peanut-GB + url = https://github.com/deltabeard/Peanut-GB diff --git a/Cargo.lock b/Cargo.lock index 8b76c5a..24d9dfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,20 +18,18 @@ version = "0.1.0" dependencies = [ "abi_sys", "embedded-graphics", + "once_cell", "rand_core 0.9.3", - "shared", - "spin", - "talc", ] [[package]] name = "abi_sys" version = "0.1.0" dependencies = [ + "bitflags 2.10.0", + "cbindgen", "defmt 0.3.100", "embedded-graphics", - "embedded-sdmmc", - "shared", "strum", ] @@ -49,9 +47,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -95,6 +93,17 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -116,6 +125,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.5.3" @@ -146,12 +161,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - [[package]] name = "bitfield" version = "0.13.2" @@ -172,9 +181,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -220,9 +229,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -240,10 +249,53 @@ dependencies = [ ] [[package]] -name = "cfg-if" -version = "1.0.3" +name = "cbindgen" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d" +dependencies = [ + "clap", + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] [[package]] name = "codespan-reporting" @@ -255,6 +307,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + [[package]] name = "cortex-m" version = "0.7.7" @@ -284,7 +342,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -380,8 +438,8 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.106", + "strsim 0.11.1", + "syn 2.0.110", ] [[package]] @@ -392,7 +450,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -430,7 +488,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -439,17 +497,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "defmt-rtt" -version = "0.4.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" dependencies = [ "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", ] [[package]] @@ -491,9 +549,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -579,7 +637,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -828,6 +886,18 @@ dependencies = [ "embedded-io-async", ] +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + [[package]] name = "embedded-audio" version = "0.1.0" @@ -907,18 +977,6 @@ dependencies = [ "nb 1.1.0", ] -[[package]] -name = "embedded-iconoir" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52b9899b636b56d4e66834f7a90766d0bc6600c0f067d91ed0711b11fa3f5c8" -dependencies = [ - "bit_field", - "embedded-graphics", - "paste", - "static_assertions", -] - [[package]] name = "embedded-io" version = "0.6.1" @@ -952,7 +1010,7 @@ checksum = "4f6e621fe4c7e05b695274b722dc0a60bacd1c8696b58191baa0154713d52400" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -971,6 +1029,7 @@ dependencies = [ [[package]] name = "embedded-snake" version = "0.0.3" +source = "git+https://github.com/LegitCamper/embedded-snake-rs#3986819c55819283dc5c1e89717d2a2f52e9b161" dependencies = [ "embedded-graphics", "rand_core 0.9.3", @@ -993,9 +1052,9 @@ dependencies = [ [[package]] name = "embedded-text" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005680edc0d075af5e02d5788ca291737bd9aba7fc404ae031cc9dfa715e5f7d" +checksum = "6cf5c72c52db2f7dbe4a9c1ed81cd21301e8d66311b194fa41c04fb4f71843ba" dependencies = [ "az", "embedded-graphics", @@ -1023,6 +1082,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fixed" version = "1.29.0" @@ -1062,12 +1137,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "funty" version = "2.0.0" @@ -1128,7 +1197,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1168,9 +1237,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1188,10 +1257,32 @@ dependencies = [ ] [[package]] -name = "goblin" -version = "0.10.1" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a80adfd63bd7ffd94fefc3d22167880c440a724303080e5aa686fa36abaa96" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "selection_ui", + "tinygif", +] + +[[package]] +name = "goblin" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51876e3748c4a347fe65b906f2b1ae46a1e55a497b22c94c1f4f2c469ff7673a" dependencies = [ "plain", "scroll", @@ -1199,12 +1290,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1216,6 +1308,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1238,16 +1336,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", - "serde", "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1262,9 +1374,19 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.11.4" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1272,13 +1394,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1300,10 +1422,16 @@ dependencies = [ ] [[package]] -name = "js-sys" -version = "0.3.80" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1324,14 +1452,15 @@ version = "0.0.1" dependencies = [ "abi_sys", "assign-resources", - "bitflags 2.9.4", + "bitflags 2.10.0", "bt-hci", "bumpalo", "cortex-m", "cortex-m-rt", + "critical-section", "cyw43", "cyw43-pio", - "defmt 0.3.100", + "defmt 1.0.1", "defmt-rtt", "embassy-embedded-hal 0.3.2", "embassy-executor", @@ -1340,6 +1469,7 @@ dependencies = [ "embassy-sync 0.7.2", "embassy-time 0.5.0", "embassy-usb", + "embedded-alloc", "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -1350,14 +1480,12 @@ dependencies = [ "embedded-text", "goblin", "heapless", - "kolibri-embedded-gui", "micromath", - "num_enum 0.7.4", + "num_enum 0.7.5", "once_cell", "panic-probe", "portable-atomic", "rand", - "shared", "spin", "st7365p-lcd", "static_cell", @@ -1366,18 +1494,6 @@ dependencies = [ "trouble-host", ] -[[package]] -name = "kolibri-embedded-gui" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011f8f415e8c2f03e4ad752afcf1bb156a18926250401b1fe29d8feda644c140" -dependencies = [ - "embedded-graphics", - "embedded-iconoir", - "foldhash", - "heapless", -] - [[package]] name = "lalrpop" version = "0.19.12" @@ -1414,7 +1530,7 @@ dependencies = [ "petgraph 0.7.1", "pico-args", "regex", - "regex-syntax 0.8.6", + "regex-syntax 0.8.8", "sha3", "string_cache", "term 1.2.0", @@ -1449,9 +1565,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" @@ -1459,23 +1575,34 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] [[package]] -name = "litrs" -version = "0.4.2" +name = "linked_list_allocator" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1487,9 +1614,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "micromath" @@ -1538,11 +1665,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ - "num_enum_derive 0.7.4", + "num_enum_derive 0.7.5", "rustversion", ] @@ -1559,13 +1686,13 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1580,6 +1707,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1592,9 +1725,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1602,15 +1735,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -1626,7 +1759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap", + "indexmap 2.12.0", ] [[package]] @@ -1636,7 +1769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap", + "indexmap 2.12.0", ] [[package]] @@ -1694,7 +1827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" dependencies = [ "arrayvec", - "num_enum 0.7.4", + "num_enum 0.7.5", "paste", ] @@ -1751,7 +1884,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1818,27 +1951,33 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -1868,11 +2007,11 @@ checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -1881,32 +2020,32 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.6", + "regex-syntax 0.8.8", ] [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.6", + "regex-syntax 0.8.8", ] [[package]] @@ -1917,17 +2056,26 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" dependencies = [ - "bytemuck", + "cfg-if", + "const-default", + "libc", + "svgbobdoc", ] [[package]] @@ -1958,12 +2106,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1985,6 +2152,16 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +[[package]] +name = "selection_ui" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics", + "embedded-layout", + "embedded-text", +] + [[package]] name = "semver" version = "0.9.0" @@ -2002,31 +2179,45 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", ] [[package]] @@ -2045,14 +2236,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "shared" -version = "0.1.0" -dependencies = [ - "bitflags 2.9.4", - "defmt 1.0.1", -] - [[package]] name = "siphasher" version = "1.0.1" @@ -2076,9 +2259,9 @@ dependencies = [ [[package]] name = "smart-leds-trait" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edeb89c73244414bb0568611690dd095b2358b3fda5bae65ad784806cca00157" +checksum = "a7f4441a131924d58da6b83a7ad765c460e64630cce504376c3a87a2558c487f" dependencies = [ "rgb", ] @@ -2127,15 +2310,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" 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" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_cell" @@ -2158,6 +2335,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -2179,10 +2362,23 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", +] + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", ] [[package]] @@ -2198,9 +2394,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2222,6 +2418,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "term" version = "0.7.0" @@ -2239,7 +2448,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.61.0", + "windows-sys", ] [[package]] @@ -2251,6 +2460,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "1.0.69" @@ -2262,11 +2477,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2277,18 +2492,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -2309,6 +2524,24 @@ dependencies = [ "embedded-graphics", ] +[[package]] +name = "tinygif" +version = "0.0.4" +source = "git+https://github.com/LegitCamper/tinygif#083c0efbdbeb21835de30425e234247340f6e370" +dependencies = [ + "embedded-graphics", + "heapless", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "trouble-host" version = "0.1.0" @@ -2338,21 +2571,21 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", "uuid", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -2467,10 +2700,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.103" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -2479,25 +2721,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2505,22 +2733,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.110", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -2557,7 +2785,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys", ] [[package]] @@ -2568,91 +2796,24 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wyz" @@ -2680,5 +2841,5 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] diff --git a/Cargo.toml b/Cargo.toml index 3e2e0ae..314eaf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,13 @@ resolver = "3" members = [ "kernel", + "abi_sys", "abi", - "shared", + "selection_ui", "user-apps/calculator", "user-apps/snake", "user-apps/gallery", + "user-apps/gif", "user-apps/wav_player", ] @@ -18,10 +20,12 @@ codegen-units = 1 [profile.release-binary] inherits = "release" +codegen-units = 1 lto = true debug = false -opt-level = "s" +opt-level = "z" +panic = "unwind" [profile.dev] lto = true -opt-level = "z" +opt-level = "s" diff --git a/README.md b/README.md index 657623e..2d4d4e0 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI ## Project Structure - **`kernel/`** – The core OS kernel (task scheduling, drivers, memory, etc.) -- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace interaction -- **`abi/`** – ABI helpers and abstractions for easier development -- **`shared/`** – Shared utilities and common code across kernel and user applications +- **`abi_sys/`** – Shared application binary interface definitions for kernel ↔ userspace (Repr "C") +- **`abi/`** – Rust focused ABI helpers and abstractions for easier development - **`user-apps/`** – Collection of userspace programs (calculator, snake, etc.) ## Features @@ -30,4 +29,6 @@ git clone https://github.com/LegitCamper/picocalc-os-rs.git cd picocalc-os-rs just userapps # copy the build applications from target/thumbv8m.main-none-eabihf/release-binary/application to the sdcard and rename them to app.bin -just kernel-release # keep in mind that https://github.com/StripedMonkey/elf2uf2-rs version is required until https://github.com/JoNil/elf2uf2-rs/pull/41 is merged + +# has builds for the official rp2350 board and the pimoroni2w board +just kernel-release rp235x # keep in mind that https://github.com/StripedMonkey/elf2uf2-rs version is required until https://github.com/JoNil/elf2uf2-rs/pull/41 is merged diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 3d46683..591ddaa 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -embedded-graphics = "0.8.1" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } -talc = "4.4.3" -spin = "0.10.0" +embedded-graphics = "0.8.1" +once_cell = { version = "1", default-features = false } rand_core = "0.9.3" diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 2f4b96a..7bd1b2d 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -1,27 +1,57 @@ #![no_std] +#![allow(static_mut_refs)] -pub use abi_sys::{ - AUDIO_BUFFER_LEN, audio_buffer_ready, file_len, get_key, list_dir, lock_display, print, - read_file, send_audio_buffer, sleep, -}; -use abi_sys::{RngRequest, draw_iter, gen_rand}; +extern crate alloc; + +use abi_sys::{RngRequest, keyboard::KeyEvent}; +pub use abi_sys::{keyboard, print}; +pub use alloc::format; +use core::alloc::{GlobalAlloc, Layout}; use rand_core::RngCore; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -use talc::*; - -static mut ARENA: [u8; 10000] = [0; 10000]; #[global_allocator] -static ALLOCATOR: Talck, ClaimOnOom> = - Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) - .lock(); +static ALLOC: Alloc = Alloc; + +struct Alloc; + +unsafe impl GlobalAlloc for Alloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + abi_sys::alloc(layout.into()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + abi_sys::dealloc(ptr, layout.into()); + } +} + +#[macro_export] +macro_rules! println { + ($($arg:tt)*) => {{ + let s = $crate::format!($($arg)*); + $crate::print(s.as_ptr(), s.len()); + }}; +} + +pub fn sleep(ms: u64) { + abi_sys::sleep(ms); +} + +pub fn get_ms() -> u64 { + abi_sys::get_ms() +} + +pub fn get_key() -> KeyEvent { + abi_sys::keyboard::get_key().into() +} pub mod display { - use crate::draw_iter; + use core::sync::atomic::{AtomicBool, Ordering}; + + use abi_sys::CPixel; use embedded_graphics::{ Pixel, geometry::{Dimensions, Point}, - pixelcolor::{Rgb565, RgbColor}, + pixelcolor::Rgb565, prelude::{DrawTarget, Size}, primitives::Rectangle, }; @@ -31,7 +61,28 @@ pub mod display { pub type Pixel565 = Pixel; - pub struct Display; + const BUF_SIZE: usize = 1024; + static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; + + static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false); + + pub struct Display { + _private: (), + } + + impl Display { + /// Only one instance of Display can be taken + pub fn take() -> Option { + if DISPLAY_TAKEN + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(Self { _private: () }) + } else { + None + } + } + } impl Dimensions for Display { fn bounding_box(&self) -> Rectangle { @@ -53,22 +104,19 @@ pub mod display { where I: IntoIterator>, { - const BUF_SIZE: usize = 1024; // tune this for performance - let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; - let mut count = 0; for p in pixels { - buf[count] = p; + unsafe { BUF[count] = p.into() }; count += 1; - if count == BUF_SIZE { - draw_iter(&buf[..count]); + if count == BUF_SIZE - 1 { + abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count); count = 0; } } if count > 0 { - draw_iter(&buf[..count]); + abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count); } Ok(()) @@ -76,6 +124,10 @@ pub mod display { } } +fn gen_rand(req: &mut RngRequest) { + abi_sys::gen_rand(req); +} + pub struct Rng; impl RngCore for Rng { @@ -104,3 +156,122 @@ impl RngCore for Rng { gen_rand(&mut req); } } + +pub mod fs { + use alloc::vec::Vec; + use core::fmt::Display; + + pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize { + abi_sys::read_file( + file.as_ptr(), + file.len(), + start_from, + buf.as_mut_ptr(), + buf.len(), + ) + } + + pub fn write_file(file: &str, start_from: usize, buf: &[u8]) { + abi_sys::write_file( + file.as_ptr(), + file.len(), + start_from, + buf.as_ptr(), + buf.len(), + ) + } + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct FileName<'a> { + full: &'a str, + base: &'a str, + ext: Option<&'a str>, + } + + impl<'a> FileName<'a> { + pub fn full_name(&self) -> &str { + self.full + } + + pub fn base(&self) -> &str { + self.base + } + + pub fn extension(&self) -> Option<&str> { + self.ext + } + } + + impl<'a> Display for FileName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.full_name()) + } + } + + impl<'a> From<&'a str> for FileName<'a> { + fn from(s: &'a str) -> FileName<'a> { + let full = s; + + // Split on last dot for extension + let (base, ext) = match s.rfind('.') { + Some(idx) => (&s[..idx], Some(&s[idx + 1..])), + None => (s, None), + }; + + FileName { full, base, ext } + } + } + + const MAX_ENTRY_NAME_LEN: usize = 25; + const MAX_ENTRIES: usize = 25; + + #[derive(Clone, Copy, Debug)] + pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); + + impl Entries { + pub fn new() -> Self { + Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]) + } + + /// Get list of file names after listing + pub fn entries<'a>(&'a self) -> Vec> { + self.0 + .iter() + .filter_map(|buf| { + let nul_pos = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + Some(core::str::from_utf8(&buf[..nul_pos]).ok()?.into()) + }) + .collect() + } + + fn as_ptrs(&mut self) -> [*mut u8; MAX_ENTRIES] { + let mut ptrs: [*mut u8; MAX_ENTRIES] = [core::ptr::null_mut(); MAX_ENTRIES]; + for (i, buf) in self.0.iter_mut().enumerate() { + ptrs[i] = buf.as_mut_ptr(); + } + ptrs + } + } + + pub fn list_dir(path: &str, entries: &mut Entries) -> usize { + abi_sys::list_dir( + path.as_ptr(), + path.len(), + entries.as_ptrs().as_mut_ptr(), + MAX_ENTRIES, + MAX_ENTRY_NAME_LEN, + ) + } + + pub fn file_len(str: &str) -> usize { + abi_sys::file_len(str.as_ptr(), str.len()) + } +} + +pub mod audio { + pub use abi_sys::{AUDIO_BUFFER_LEN, audio_buffer_ready}; + + pub fn send_audio_buffer(buf: &[u8]) { + abi_sys::send_audio_buffer(buf.as_ptr(), buf.len()) + } +} diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index ee7bad7..5a83d0b 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2024" [features] +default = ["alloc"] +alloc = [] defmt = ["dep:defmt"] [dependencies] -embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } +bitflags = "2.9.4" +embedded-graphics = "0.8.1" defmt = { version = "0.3", optional = true } -shared = { path = "../shared" } -embedded-sdmmc = { version = "0.9.0", default-features = false } + +[build-dependencies] +cbindgen = "0.24.0" diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index f40a74f..82f2f6e 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -1,85 +1,382 @@ #![no_std] +#[cfg(feature = "alloc")] +use core::alloc::Layout; -extern crate alloc; - -#[allow(unused)] +use core::ffi::c_char; use embedded_graphics::{ Pixel, - geometry::Point, - pixelcolor::{Rgb565, RgbColor}, + pixelcolor::{Rgb565, raw::RawU16}, + prelude::{IntoStorage, Point}, }; -use embedded_sdmmc::DirEntry; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); +pub const ABI_CALL_TABLE_COUNT: usize = 14; +const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT); + +#[derive(Clone, Copy, EnumIter, EnumCount)] +#[repr(u8)] +pub enum CallTable { + Alloc = 0, + Dealloc = 1, + PrintString = 2, + SleepMs = 3, + GetMs = 4, + DrawIter = 5, + GetKey = 6, + GenRand = 7, + ListDir = 8, + ReadFile = 9, + WriteFile = 10, + FileLen = 11, + AudioBufferReady = 12, + SendAudioBuffer = 13, +} + #[unsafe(no_mangle)] #[unsafe(link_section = ".syscall_table")] -pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; +pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT]; -#[repr(usize)] -#[derive(Clone, Copy, EnumIter, EnumCount)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CallAbiTable { - PrintString = 0, - SleepMs = 1, - LockDisplay = 2, - DrawIter = 3, - GetKey = 4, - GenRand = 5, - ListDir = 6, - ReadFile = 7, - FileLen = 8, - AudioBufferReady = 9, - SendAudioBuffer = 10, +#[cfg(feature = "alloc")] +#[repr(C)] +pub struct CLayout { + size: usize, + alignment: usize, +} + +#[cfg(feature = "alloc")] +impl Into for CLayout { + fn into(self) -> Layout { + unsafe { Layout::from_size_align_unchecked(self.size, self.alignment) } + } +} + +#[cfg(feature = "alloc")] +impl From for CLayout { + fn from(value: Layout) -> Self { + Self { + size: value.size(), + alignment: value.align(), + } + } +} + +pub type AllocAbi = extern "C" fn(layout: CLayout) -> *mut u8; + +#[unsafe(no_mangle)] +pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { + let f: AllocAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Alloc as usize]) }; + f(layout) +} + +pub type DeallocAbi = extern "C" fn(ptr: *mut u8, layout: CLayout); + +#[unsafe(no_mangle)] +pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { + let f: DeallocAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) }; + f(ptr, layout) } pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize); -#[allow(unused)] -pub fn print(msg: &str) { +#[unsafe(no_mangle)] +pub extern "C" fn print(ptr: *const u8, len: usize) { let f: PrintAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::PrintString as usize]) }; - f(msg.as_ptr(), msg.len()); + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) }; + f(ptr, len); } -pub type SleepAbi = extern "C" fn(ms: u64); +pub type SleepMsAbi = extern "C" fn(ms: u64); -#[allow(unused)] -pub fn sleep(ms: u64) { - let f: SleepAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::SleepMs as usize]) }; +#[unsafe(no_mangle)] +pub extern "C" fn sleep(ms: u64) { + let f: SleepMsAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) }; f(ms); } -pub type LockDisplay = extern "C" fn(lock: bool); +pub type GetMsAbi = extern "C" fn() -> u64; -#[allow(unused)] -pub fn lock_display(lock: bool) { - let f: LockDisplay = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::LockDisplay as usize]) }; - f(lock); -} - -pub type DrawIterAbi = extern "C" fn(ptr: *const Pixel, len: usize); - -#[allow(unused)] -pub fn draw_iter(pixels: &[Pixel]) { - let f: DrawIterAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]) }; - f(pixels.as_ptr(), pixels.len()); -} - -pub type GetKeyAbi = extern "C" fn() -> KeyEvent; - -#[allow(unused)] -pub fn get_key() -> KeyEvent { - let f: GetKeyAbi = - unsafe { core::mem::transmute(CALL_ABI_TABLE[CallAbiTable::GetKey as usize]) }; +#[unsafe(no_mangle)] +pub extern "C" fn get_ms() -> u64 { + let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetMs as usize]) }; f() } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct CPixel { + pub x: i32, + pub y: i32, + pub color: u16, +} + +impl CPixel { + pub const fn new() -> Self { + Self { + x: 0, + y: 0, + color: 0, + } + } +} + +impl Into for Pixel { + fn into(self) -> CPixel { + CPixel { + x: self.0.x, + y: self.0.y, + color: self.1.into_storage(), + } + } +} + +impl Into> for CPixel { + fn into(self) -> Pixel { + Pixel(Point::new(self.x, self.y), RawU16::new(self.color).into()) + } +} + +pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize); + +#[unsafe(no_mangle)] +pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { + let f: DrawIterAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) }; + f(ptr, len); +} + +pub mod keyboard { + use crate::{CALL_ABI_TABLE, CallTable}; + + bitflags::bitflags! { + #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + #[repr(C)] + pub struct Modifiers: u8 { + const NONE = 0; + const CTRL = 1; + const ALT = 2; + const LSHIFT = 4; + const RSHIFT = 8; + const SYM = 16; + } + } + + #[repr(C)] + pub struct KeyEventC { + pub key: u8, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEventC { + fn into(self) -> KeyEvent { + KeyEvent { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[derive(Debug)] + pub struct KeyEvent { + pub key: KeyCode, + pub state: KeyState, + pub mods: Modifiers, + } + + impl Into for KeyEvent { + fn into(self) -> KeyEventC { + KeyEventC { + key: self.key.into(), + state: self.state, + mods: self.mods, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(C)] + pub enum KeyState { + Idle = 0, + Pressed = 1, + Hold = 2, + Released = 3, + } + + impl From for KeyState { + fn from(value: u8) -> Self { + match value { + 1 => KeyState::Pressed, + 2 => KeyState::Hold, + 3 => KeyState::Released, + 0 | _ => KeyState::Idle, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + pub enum KeyCode { + JoyUp = 0x01, + JoyDown = 0x02, + JoyLeft = 0x03, + JoyRight = 0x04, + JoyCenter = 0x05, + BtnLeft1 = 0x06, + BtnRight1 = 0x07, + BtnLeft2 = 0x11, + BtnRight2 = 0x12, + Backspace = 0x08, + Tab = 0x09, + Enter = 0x0A, + ModAlt = 0xA1, + ModShiftLeft = 0xA2, + ModShiftRight = 0xA3, + ModSym = 0xA4, + ModCtrl = 0xA5, + Esc = 0xB1, + Left = 0xB4, + Up = 0xB5, + Down = 0xB6, + Right = 0xB7, + Break = 0xD0, + Insert = 0xD1, + Home = 0xD2, + Del = 0xD4, + End = 0xD5, + PageUp = 0xD6, + PageDown = 0xD7, + CapsLock = 0xC1, + F1 = 0x81, + F2 = 0x82, + F3 = 0x83, + F4 = 0x84, + F5 = 0x85, + F6 = 0x86, + F7 = 0x87, + F8 = 0x88, + F9 = 0x89, + F10 = 0x90, + Char(char), + Unknown(u8), + } + + impl Into for KeyCode { + fn into(self) -> u8 { + match self { + KeyCode::JoyUp => 0x01, + KeyCode::JoyDown => 0x02, + KeyCode::JoyLeft => 0x03, + KeyCode::JoyRight => 0x04, + KeyCode::JoyCenter => 0x05, + KeyCode::BtnLeft1 => 0x06, + KeyCode::BtnRight1 => 0x07, + KeyCode::BtnLeft2 => 0x11, + KeyCode::BtnRight2 => 0x12, + KeyCode::Backspace => 0x08, + KeyCode::Tab => 0x09, + KeyCode::Enter => 0x0A, + KeyCode::ModAlt => 0xA1, + KeyCode::ModShiftLeft => 0xA2, + KeyCode::ModShiftRight => 0xA3, + KeyCode::ModSym => 0xA4, + KeyCode::ModCtrl => 0xA5, + KeyCode::Esc => 0xB1, + KeyCode::Left => 0xB4, + KeyCode::Up => 0xB5, + KeyCode::Down => 0xB6, + KeyCode::Right => 0xB7, + KeyCode::Break => 0xD0, + KeyCode::Insert => 0xD1, + KeyCode::Home => 0xD2, + KeyCode::Del => 0xD4, + KeyCode::End => 0xD5, + KeyCode::PageUp => 0xD6, + KeyCode::PageDown => 0xD7, + KeyCode::CapsLock => 0xC1, + KeyCode::F1 => 0x81, + KeyCode::F2 => 0x82, + KeyCode::F3 => 0x83, + KeyCode::F4 => 0x84, + KeyCode::F5 => 0x85, + KeyCode::F6 => 0x86, + KeyCode::F7 => 0x87, + KeyCode::F8 => 0x88, + KeyCode::F9 => 0x89, + KeyCode::F10 => 0x90, + KeyCode::Char(char) => char as u8, + KeyCode::Unknown(i) => i, + } + } + } + + impl From for KeyCode { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::JoyUp, + 0x02 => Self::JoyDown, + 0x03 => Self::JoyLeft, + 0x04 => Self::JoyRight, + 0x05 => Self::JoyCenter, + 0x06 => Self::BtnLeft1, + 0x07 => Self::BtnRight1, + 0x08 => Self::Backspace, + 0x09 => Self::Tab, + 0x0A => Self::Enter, + 0x11 => Self::BtnLeft2, + 0x12 => Self::BtnRight2, + 0xA1 => Self::ModAlt, + 0xA2 => Self::ModShiftLeft, + 0xA3 => Self::ModShiftRight, + 0xA4 => Self::ModSym, + 0xA5 => Self::ModCtrl, + 0xB1 => Self::Esc, + 0xB4 => Self::Left, + 0xB5 => Self::Up, + 0xB6 => Self::Down, + 0xB7 => Self::Right, + 0xC1 => Self::CapsLock, + 0xD0 => Self::Break, + 0xD1 => Self::Insert, + 0xD2 => Self::Home, + 0xD4 => Self::Del, + 0xD5 => Self::End, + 0xD6 => Self::PageUp, + 0xD7 => Self::PageDown, + 0x81 => Self::F1, + 0x82 => Self::F2, + 0x83 => Self::F3, + 0x84 => Self::F4, + 0x85 => Self::F5, + 0x86 => Self::F6, + 0x87 => Self::F7, + 0x88 => Self::F8, + 0x89 => Self::F9, + 0x90 => Self::F10, + _ => match char::from_u32(value as u32) { + Some(c) => Self::Char(c), + None => Self::Unknown(value), + }, + } + } + } + + pub type GetKeyAbi = extern "C" fn() -> KeyEventC; + + #[unsafe(no_mangle)] + pub extern "C" fn get_key() -> KeyEventC { + let f: GetKeyAbi = + unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) }; + f() + } +} + #[repr(C)] pub enum RngRequest { U32(u32), @@ -89,10 +386,10 @@ pub enum RngRequest { pub type GenRand = extern "C" fn(req: &mut RngRequest); -#[allow(unused)] -pub fn gen_rand(req: &mut RngRequest) { +#[unsafe(no_mangle)] +pub extern "C" fn gen_rand(req: &mut RngRequest) { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize]; + let ptr = CALL_ABI_TABLE[CallTable::GenRand as usize]; let f: GenRand = core::mem::transmute(ptr); f(req) } @@ -101,16 +398,23 @@ pub fn gen_rand(req: &mut RngRequest) { pub type ListDir = extern "C" fn( str: *const u8, len: usize, - files: *mut Option, + entries: *mut *mut c_char, file_len: usize, + max_entry_str_len: usize, ) -> usize; -#[allow(unused)] -pub fn list_dir(path: &str, files: &mut [Option]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn list_dir( + str: *const u8, + len: usize, + entries: *mut *mut c_char, + entry_count: usize, + max_entry_str_len: usize, +) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize]; + let ptr = CALL_ABI_TABLE[CallTable::ListDir as usize]; let f: ListDir = core::mem::transmute(ptr); - f(path.as_ptr(), path.len(), files.as_mut_ptr(), files.len()) + f(str, len, entries, entry_count, max_entry_str_len) } } @@ -122,29 +426,47 @@ pub type ReadFile = extern "C" fn( buf_len: usize, ) -> usize; -#[allow(unused)] -pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn read_file( + str: *const u8, + len: usize, + read_from: usize, + buf: *mut u8, + buf_len: usize, +) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::ReadFile as usize]; + let ptr = CALL_ABI_TABLE[CallTable::ReadFile as usize]; let f: ReadFile = core::mem::transmute(ptr); - f( - file.as_ptr(), - file.len(), - read_from, - buf.as_mut_ptr(), - buf.len(), - ) + f(str, len, read_from, buf, buf_len) + } +} + +pub type WriteFile = + extern "C" fn(str: *const u8, len: usize, write_from: usize, buf: *const u8, buf_len: usize); + +#[unsafe(no_mangle)] +pub extern "C" fn write_file( + str: *const u8, + len: usize, + write_from: usize, + buf: *const u8, + buf_len: usize, +) { + unsafe { + let ptr = CALL_ABI_TABLE[CallTable::WriteFile as usize]; + let f: WriteFile = core::mem::transmute(ptr); + f(str, len, write_from, buf, buf_len) } } pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; -#[allow(unused)] -pub fn file_len(file: &str) -> usize { +#[unsafe(no_mangle)] +pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize]; + let ptr = CALL_ABI_TABLE[CallTable::FileLen as usize]; let f: FileLen = core::mem::transmute(ptr); - f(file.as_ptr(), file.len()) + f(str, len) } } @@ -153,7 +475,7 @@ pub type AudioBufferReady = extern "C" fn() -> bool; #[allow(unused)] pub fn audio_buffer_ready() -> bool { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::AudioBufferReady as usize]; + let ptr = CALL_ABI_TABLE[CallTable::AudioBufferReady as usize]; let f: AudioBufferReady = core::mem::transmute(ptr); f() } @@ -164,10 +486,10 @@ pub const AUDIO_BUFFER_LEN: usize = 1024; pub type SendAudioBuffer = extern "C" fn(ptr: *const u8, len: usize); #[allow(unused)] -pub fn send_audio_buffer(buf: &[u8; AUDIO_BUFFER_LEN]) { +pub fn send_audio_buffer(buf: *const u8, len: usize) { unsafe { - let ptr = CALL_ABI_TABLE[CallAbiTable::SendAudioBuffer as usize]; + let ptr = CALL_ABI_TABLE[CallTable::SendAudioBuffer as usize]; let f: SendAudioBuffer = core::mem::transmute(ptr); - f(buf.as_ptr(), buf.len()) + f(buf, len) } } diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..56fe889 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,4 @@ +language = "C" + +[macro_expansion] +bitflags = true diff --git a/justfile b/justfile index 6aa314c..50dd42f 100644 --- a/justfile +++ b/justfile @@ -1,16 +1,57 @@ -kernel-dev: - cargo run --bin kernel -kernel-release: - cargo build --bin kernel --release +kernel-dev board: + cargo run --bin kernel --features {{board}} --features fps +kernel-release-probe board: + cargo run --bin kernel --profile release --features {{board}} --features fps +kernel-release board: + cargo build --bin kernel --release --no-default-features --features {{board}} elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" +cbindgen: + cbindgen abi_sys --output abi_sys.h -q + +newlib: + #!/bin/bash + cd picolibc + mkdir build + cd build + CONFIG_PICOLIBC=true ../scripts/do-configure thumbv8m_main_fp-none-eabi \ + --buildtype=minsize \ + -Dtests=true \ + -Dtinystdio=false \ + -Dsingle-thread=true \ + -Db_pie=true \ + -Ddefault_library=static \ + -Dtinystdio=false \ + -Dnewlib-nano-malloc=true \ + -Dmultilib=false \ + -Dpicolib=true \ + "$@" + DESTDIR=./install meson install + ninja + userapp app: {{binary-args}} cargo build --bin {{app}} --profile release-binary -userapps: +userapps: cbindgen just userapp calculator just userapp snake just userapp gallery + just userapp gif just userapp wav_player + +copy-userapp app: + cp ./target/thumbv8m.main-none-eabihf/release-binary/{{app}} /run/media/$(whoami)/PICOCALC/{{app}}.bin + +copy-userapps: + #!/bin/bash + just userapps + just copy-userapp calculator + just copy-userapp snake + just copy-userapp gallery + just copy-userapp gif + + DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}') + udisksctl unmount -b "$DEV" + udisksctl power-off -b "$DEV" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d8ccc74..8293a53 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -12,13 +12,15 @@ bench = false [features] default = ["rp235x", "defmt"] -rp2040 = ["embassy-rp/rp2040"] +pimoroni2w = ["rp235x", "psram"] +# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb rp235x = ["embassy-rp/rp235xb"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] +psram = ["dep:embedded-alloc"] +overclock = [] +fps = [] defmt = [ "dep:defmt", - "shared/defmt", - "abi_sys/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", @@ -60,6 +62,7 @@ cyw43 = { version = "0.3.0", features = [ ], optional = true } cyw43-pio = { version = "0.3.0", optional = true } +critical-section = "1.2.0" embedded-hal-bus = { version = "0.3.0", features = ["async"] } embedded-hal = "0.2.7" embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" } @@ -70,15 +73,14 @@ panic-probe = "0.3" portable-atomic = { version = "1.11", features = ["critical-section"] } assign-resources = "0.5.0" -defmt = { version = "0.3", optional = true } -defmt-rtt = "0.4.2" +defmt = { version = "1.0.1", optional = true } +defmt-rtt = "1.1.0" embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch embedded-graphics = { version = "0.8.1" } embedded-text = "0.7.2" embedded-layout = "0.4.2" -kolibri-embedded-gui = "0.1.0" strum = { version = "0.27.2", default-features = false } rand = { version = "0.9.0", default-features = false } @@ -90,8 +92,10 @@ spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" +embedded-alloc = { version = "0.6.0", features = [ + "allocator_api", +], optional = true } bumpalo = "3.19.0" -shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } micromath = "2.1.0" diff --git a/kernel/build.rs b/kernel/build.rs index 30691aa..8220df0 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -13,13 +13,18 @@ use std::fs::File; use std::io::Write; use std::path::PathBuf; +#[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))] +const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); +#[cfg(feature = "pimoroni2w")] +const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); + fn main() { // Put `memory.x` in our output directory and ensure it's // on the linker search path. let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); File::create(out.join("memory.x")) .unwrap() - .write_all(include_bytes!("memory.x")) + .write_all(MEMORY) .unwrap(); println!("cargo:rustc-link-search={}", out.display()); diff --git a/kernel/pimoroni2w.x b/kernel/pimoroni2w.x new file mode 100644 index 0000000..a8beacb --- /dev/null +++ b/kernel/pimoroni2w.x @@ -0,0 +1,60 @@ +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 16M - 4K + + RAM : ORIGIN = 0x20000000, LENGTH = 512K + # SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/kernel/memory.x b/kernel/rp2350.x similarity index 100% rename from kernel/memory.x rename to kernel/rp2350.x diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index b88a998..6254934 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,34 +1,74 @@ use abi_sys::{ - AUDIO_BUFFER_LEN, AudioBufferReady, DrawIterAbi, FileLen, GenRand, GetKeyAbi, ListDir, - LockDisplay, Modifiers, PrintAbi, ReadFile, RngRequest, SendAudioBuffer, SleepAbi, + AUDIO_BUFFER_LEN, AllocAbi, AudioBufferReady, CLayout, CPixel, DeallocAbi, DrawIterAbi, + FileLen, GenRand, GetMsAbi, ListDir, PrintAbi, ReadFile, RngRequest, SendAudioBuffer, + SleepMsAbi, WriteFile, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; -use core::sync::atomic::Ordering; +use core::{ffi::c_char, ptr, sync::atomic::Ordering}; use embassy_rp::clocks::{RoscRng, clk_sys_freq}; -use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565}; -use embedded_sdmmc::{DirEntry, LfnBuffer}; +use embassy_time::Instant; +use embedded_graphics::draw_target::DrawTarget; +use embedded_sdmmc::LfnBuffer; use heapless::spsc::Queue; -use shared::keyboard::KeyEvent; + +#[cfg(feature = "psram")] +use crate::heap::HEAP; + +#[cfg(feature = "psram")] +use core::alloc::GlobalAlloc; use crate::{ audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY}, - display::{FB_PAUSED, FRAMEBUFFER}, + display::FRAMEBUFFER, + framebuffer::FB_PAUSED, storage::{Dir, File, SDCARD}, }; +const _: AllocAbi = alloc; +pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { + // SAFETY: caller guarantees layout is valid + unsafe { + #[cfg(feature = "psram")] + { + return HEAP.alloc(layout.into()); + } + + #[cfg(not(feature = "psram"))] + { + return alloc::alloc::alloc(layout.into()); + } + } +} + +const _: DeallocAbi = dealloc; +pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { + // SAFETY: caller guarantees ptr and layout are valid + #[cfg(feature = "psram")] + { + unsafe { HEAP.dealloc(ptr, layout.into()) } + } + + #[cfg(not(feature = "psram"))] + { + unsafe { alloc::alloc::dealloc(ptr, layout.into()) } + } +} + const _: PrintAbi = print; pub extern "C" fn print(ptr: *const u8, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; - if let Ok(msg) = core::str::from_utf8(slice) { - defmt::info!("print: {}", msg); + if let Ok(_msg) = core::str::from_utf8(slice) { + #[cfg(feature = "defmt")] + defmt::info!("print: {}", _msg); } else { + #[cfg(feature = "defmt")] defmt::warn!("print: "); } } -const _: SleepAbi = sleep; +const _: SleepMsAbi = sleep; pub extern "C" fn sleep(ms: u64) { let cycles_per_ms = clk_sys_freq() / 1000; let total_cycles = ms * cycles_per_ms as u64; @@ -38,31 +78,40 @@ pub extern "C" fn sleep(ms: u64) { } } -const _: LockDisplay = lock_display; -pub extern "C" fn lock_display(lock: bool) { - FB_PAUSED.store(lock, Ordering::Relaxed); +pub static mut MS_SINCE_LAUNCH: Option = None; + +const _: GetMsAbi = get_ms; +pub extern "C" fn get_ms() -> u64 { + Instant::now() + .duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() }) + .as_millis() } const _: DrawIterAbi = draw_iter; -// TODO: maybe return result -pub extern "C" fn draw_iter(pixels: *const Pixel, len: usize) { +pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let pixels = unsafe { core::slice::from_raw_parts(pixels, len) }; - unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } + let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; + + let iter = cpixels.iter().copied().map(|c: CPixel| c.into()); + + FB_PAUSED.store(true, Ordering::Release); + unsafe { FRAMEBUFFER.as_mut().unwrap().draw_iter(iter).unwrap() } + FB_PAUSED.store(false, Ordering::Release); } pub static mut KEY_CACHE: Queue = Queue::new(); const _: GetKeyAbi = get_key; -pub extern "C" fn get_key() -> KeyEvent { +pub extern "C" fn get_key() -> KeyEventC { if let Some(event) = unsafe { KEY_CACHE.dequeue() } { - event + event.into() } else { KeyEvent { - key: abi_sys::KeyCode::Unknown(0), - state: abi_sys::KeyState::Idle, + key: KeyCode::Unknown(0), + state: KeyState::Idle, mods: Modifiers::empty(), } + .into() } } @@ -81,11 +130,27 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) { } } -fn get_dir_entries(dir: &Dir, files: &mut [Option]) -> usize { +unsafe fn copy_entry_to_user_buf(name: &[u8], dest: *mut c_char, max_str_len: usize) { + if !dest.is_null() { + let len = name.len().min(max_str_len - 1); + unsafe { + ptr::copy_nonoverlapping(name.as_ptr(), dest as *mut u8, len); + *dest.add(len) = 0; // nul terminator + } + } +} + +unsafe fn get_dir_entries(dir: &Dir, entries: &mut [*mut c_char], max_str_len: usize) -> usize { + let mut b = [0; 25]; + let mut buf = LfnBuffer::new(&mut b); let mut i = 0; - dir.iterate_dir(|entry| { - if i < files.len() { - files[i] = Some(entry.clone()); + dir.iterate_dir_lfn(&mut buf, |entry, lfn_name| { + if i < entries.len() { + if let Some(name) = lfn_name { + unsafe { copy_entry_to_user_buf(name.as_bytes(), entries[i], max_str_len) }; + } else { + unsafe { copy_entry_to_user_buf(entry.name.base_name(), entries[i], max_str_len) }; + } i += 1; } }) @@ -93,24 +158,30 @@ fn get_dir_entries(dir: &Dir, files: &mut [Option]) -> usize { i } -fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option]) -> usize { +unsafe fn recurse_dir( + dir: &Dir, + dirs: &[&str], + entries: &mut [*mut c_char], + max_str_len: usize, +) -> usize { if dirs.is_empty() { - return get_dir_entries(dir, files); + return unsafe { get_dir_entries(dir, entries, max_str_len) }; } let dir = dir.open_dir(dirs[0]).unwrap(); - recurse_dir(&dir, &dirs[1..], files) + unsafe { recurse_dir(&dir, &dirs[1..], entries, max_str_len) } } const _: ListDir = list_dir; pub extern "C" fn list_dir( dir: *const u8, len: usize, - files: *mut Option, + entries: *mut *mut c_char, files_len: usize, + max_entry_str_len: usize, ) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes - let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) }; + let files = unsafe { core::slice::from_raw_parts_mut(entries, files_len) }; // SAFETY: caller guarantees `ptr` is valid for `len` bytes let dir = unsafe { core::str::from_raw_parts(dir, len) }; let dirs: Vec<&str> = dir.split('/').collect(); @@ -121,10 +192,12 @@ pub extern "C" fn list_dir( let mut wrote = 0; sd.access_root_dir(|root| { if dirs[0] == "" && dirs.len() >= 2 { - if dir == "/" { - wrote = get_dir_entries(&root, files); - } else { - wrote = recurse_dir(&root, &dirs[1..], files); + unsafe { + if dir == "/" { + wrote = get_dir_entries(&root, files, max_entry_str_len); + } else { + wrote = recurse_dir(&root, &dirs[1..], files, max_entry_str_len); + } } } }); @@ -136,6 +209,7 @@ fn recurse_file( dirs: &[&str], mut access: impl FnMut(&mut File) -> T, ) -> Result { + defmt::info!("dir: {}, dirs: {}", dir, dirs); if dirs.len() == 1 { let mut b = [0_u8; 50]; let mut buf = LfnBuffer::new(&mut b); @@ -147,7 +221,8 @@ fn recurse_file( } } }) - .unwrap(); + .expect("Failed to iterate dir"); + if let Some(name) = short_name { let mut file = dir .open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend) @@ -171,7 +246,17 @@ pub extern "C" fn read_file( ) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; - let file: Vec<&str> = file.split('/').collect(); + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + // SAFETY: caller guarantees `ptr` is valid for `len` bytes let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; @@ -181,10 +266,8 @@ pub extern "C" fn read_file( let sd = guard.as_mut().unwrap(); if !file.is_empty() { sd.access_root_dir(|root| { - if let Ok(result) = recurse_file(&root, &file[1..], |file| { - if file.offset() as usize != start_from { - file.seek_from_start(start_from as u32).unwrap(); - } + if let Ok(result) = recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap_or(()); file.read(&mut buf).unwrap() }) { read = result @@ -194,9 +277,46 @@ pub extern "C" fn read_file( read } +const _: WriteFile = write_file; +pub extern "C" fn write_file( + str: *const u8, + len: usize, + start_from: usize, + buf: *const u8, + buf_len: usize, +) { + // SAFETY: caller guarantees str ptr is valid for `len` bytes + let file = unsafe { core::str::from_raw_parts(str, len) }; + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + + // SAFETY: caller guarantees buf ptr is valid for `buf_len` bytes + let buf = unsafe { core::slice::from_raw_parts(buf, buf_len) }; + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + if !file.is_empty() { + sd.access_root_dir(|root| { + recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap(); + file.write(&buf).unwrap() + }) + .unwrap_or(()) + }); + }; +} + const _: FileLen = file_len; pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { - // SAFETY: caller guarantees `ptr` is valid for `len` bytes + // SAFETY: caller guarantees str ptr is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; let file: Vec<&str> = file.split('/').collect(); diff --git a/kernel/src/display.rs b/kernel/src/display.rs index ee7996b..86c693f 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -1,6 +1,6 @@ -use core::sync::atomic::{AtomicBool, Ordering}; - -use crate::framebuffer::AtomicFrameBuffer; +use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; +use core::alloc::{GlobalAlloc, Layout}; +use core::sync::atomic::Ordering; use embassy_rp::{ Peri, gpio::{Level, Output}, @@ -8,9 +8,19 @@ use embassy_rp::{ spi::{Async, Spi}, }; use embassy_time::{Delay, Timer}; +use embedded_graphics::{ + pixelcolor::Rgb565, + prelude::{DrawTarget, RgbColor}, +}; use embedded_hal_bus::spi::ExclusiveDevice; use st7365p_lcd::ST7365P; +#[cfg(feature = "psram")] +use crate::heap::HEAP; + +#[cfg(feature = "fps")] +pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER}; + type DISPLAY = ST7365P< ExclusiveDevice, Output<'static>, Delay>, Output<'static>, @@ -21,8 +31,27 @@ type DISPLAY = ST7365P< pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_HEIGHT: usize = 320; -pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new(); -pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); +pub static mut FRAMEBUFFER: Option = None; + +fn init_fb() { + unsafe { + #[cfg(feature = "psram")] + { + let slab = HEAP.alloc(Layout::array::(framebuffer::SIZE).unwrap()) as *mut u16; + let buf = core::slice::from_raw_parts_mut(slab, framebuffer::SIZE); + + let mut fb = AtomicFrameBuffer::new(buf); + fb.clear(Rgb565::BLACK).unwrap(); + FRAMEBUFFER = Some(fb); + } + + #[cfg(not(feature = "psram"))] + { + static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE]; + FRAMEBUFFER = Some(AtomicFrameBuffer::new(&mut BUF)); + } + } +} pub async fn init_display( spi: Spi<'static, SPI1, Async>, @@ -30,6 +59,8 @@ pub async fn init_display( data: Peri<'static, PIN_14>, reset: Peri<'static, PIN_15>, ) -> DISPLAY { + init_fb(); + let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); let mut display = ST7365P::new( spi_device, @@ -41,7 +72,14 @@ pub async fn init_display( ); display.init().await.unwrap(); display.set_custom_orientation(0x40).await.unwrap(); - unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } + unsafe { + FRAMEBUFFER + .as_mut() + .unwrap() + .draw(&mut display) + .await + .unwrap() + } display.set_on().await.unwrap(); display @@ -49,16 +87,37 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { + use embassy_time::{Instant, Timer}; + + // Target ~60 Hz refresh (≈16.67 ms per frame) + const FRAME_TIME_MS: u64 = 1000 / 60; + loop { - if !FB_PAUSED.load(Ordering::Acquire) { - unsafe { - FRAMEBUFFER - .partial_draw_batched(&mut display) - .await - .unwrap() + let start = Instant::now(); + + #[cfg(feature = "fps")] + unsafe { + if FPS_COUNTER.should_draw() { + FPS_CANVAS.draw_fps().await; } } - Timer::after_millis(10).await; + if !FB_PAUSED.load(Ordering::Acquire) { + unsafe { + FRAMEBUFFER + .as_mut() + .unwrap() + .partial_draw(&mut display) + .await + .unwrap(); + } + } + + let elapsed = start.elapsed().as_millis() as u64; + if elapsed < FRAME_TIME_MS { + Timer::after_millis(FRAME_TIME_MS - elapsed).await; + } else { + Timer::after_millis(1).await; + } } } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 397baca..11e4855 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -2,7 +2,7 @@ use crate::{ abi, storage::{File, SDCARD}, }; -use abi_sys::{CallAbiTable, EntryFn}; +use abi_sys::{CallTable, EntryFn}; use alloc::{vec, vec::Vec}; use bumpalo::Bump; use core::ptr; @@ -193,19 +193,22 @@ fn patch_abi( unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } as *mut usize; - for (idx, call) in CallAbiTable::iter().enumerate() { + for (idx, call) in CallTable::iter().enumerate() { let ptr = match call { - CallAbiTable::PrintString => abi::print as usize, - CallAbiTable::SleepMs => abi::sleep as usize, - CallAbiTable::LockDisplay => abi::lock_display as usize, - CallAbiTable::DrawIter => abi::draw_iter as usize, - CallAbiTable::GetKey => abi::get_key as usize, - CallAbiTable::GenRand => abi::gen_rand as usize, - CallAbiTable::ListDir => abi::list_dir as usize, - CallAbiTable::ReadFile => abi::read_file as usize, - CallAbiTable::FileLen => abi::file_len as usize, - CallAbiTable::AudioBufferReady => abi::audio_buffer_ready as usize, - CallAbiTable::SendAudioBuffer => abi::send_audio_buffer as usize, + CallTable::Alloc => abi::alloc as usize, + CallTable::Dealloc => abi::dealloc as usize, + CallTable::PrintString => abi::print as usize, + CallTable::SleepMs => abi::sleep as usize, + CallTable::GetMs => abi::get_ms as usize, + CallTable::DrawIter => abi::draw_iter as usize, + CallTable::GetKey => abi::get_key as usize, + CallTable::GenRand => abi::gen_rand as usize, + CallTable::ListDir => abi::list_dir as usize, + CallTable::ReadFile => abi::read_file as usize, + CallTable::WriteFile => abi::write_file as usize, + CallTable::FileLen => abi::file_len as usize, + CallTable::AudioBufferReady => abi::audio_buffer_ready as usize, + CallTable::SendAudioBuffer => abi::send_audio_buffer as usize, }; unsafe { table_base.add(idx as usize).write(ptr); diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs index 04823f8..994b4b4 100644 --- a/kernel/src/framebuffer.rs +++ b/kernel/src/framebuffer.rs @@ -1,6 +1,5 @@ use crate::display::{SCREEN_HEIGHT, SCREEN_WIDTH}; use core::sync::atomic::{AtomicBool, Ordering}; -use embassy_sync::lazy_lock::LazyLock; use embedded_graphics::{ draw_target::DrawTarget, pixelcolor::{ @@ -12,34 +11,38 @@ use embedded_graphics::{ }; use embedded_hal_2::digital::OutputPin; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; -use heapless::Vec; use st7365p_lcd::ST7365P; -pub const TILE_SIZE: usize = 16; // 16x16 tile -pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles +#[cfg(feature = "fps")] +use fps::{FPS_CANVAS, FPS_CANVAS_HEIGHT, FPS_CANVAS_WIDTH, FPS_CANVAS_X, FPS_CANVAS_Y}; -// Group of tiles for batching -pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer -type MetaTileVec = heapless::Vec; +const TILE_SIZE: usize = 16; // 16x16 tile +const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles +const NUM_TILE_ROWS: usize = SCREEN_WIDTH / TILE_SIZE; +const NUM_TILE_COLS: usize = SCREEN_WIDTH / TILE_SIZE; -const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; +const MAX_BATCH_TILES: usize = (SCREEN_WIDTH / TILE_SIZE) * 2; +type BatchTileBuf = [u16; MAX_BATCH_TILES * TILE_SIZE * TILE_SIZE]; -static mut BUFFER: [u16; SIZE] = [0; SIZE]; +pub const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; -static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { - let mut tiles = Vec::new(); - for _ in 0..TILE_COUNT { - tiles.push(AtomicBool::new(true)).unwrap(); - } - tiles -}); +pub static FB_PAUSED: AtomicBool = AtomicBool::new(false); #[allow(dead_code)] -pub struct AtomicFrameBuffer; +pub struct AtomicFrameBuffer<'a> { + fb: &'a mut [u16], + dirty_tiles: [AtomicBool; TILE_COUNT], + batch_tile_buf: BatchTileBuf, +} -impl AtomicFrameBuffer { - pub const fn new() -> Self { - Self +impl<'a> AtomicFrameBuffer<'a> { + pub fn new(buffer: &'a mut [u16]) -> Self { + assert!(buffer.len() == SIZE); + Self { + fb: buffer, + dirty_tiles: core::array::from_fn(|_| AtomicBool::new(true)), + batch_tile_buf: [0; MAX_BATCH_TILES * TILE_SIZE * TILE_SIZE], + } } fn mark_tiles_dirty(&mut self, rect: Rectangle) { @@ -52,18 +55,12 @@ impl AtomicFrameBuffer { for ty in start_ty..=end_ty { for tx in start_tx..=end_tx { let tile_idx = ty * tiles_x + tx; - unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Relaxed) }; + self.dirty_tiles[tile_idx].store(true, Ordering::Release); } } } - fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> { - unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; - - Ok(()) - } - - fn set_pixels_buffered>( + fn set_pixels>( &mut self, sx: u16, sy: u16, @@ -84,7 +81,7 @@ impl AtomicFrameBuffer { for y in sy..=ey { for x in sx..=ex { if let Some(color) = color_iter.next() { - unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + self.fb[(y as usize * SCREEN_WIDTH) + x as usize] = color; } else { return Err(()); // Not enough data } @@ -99,60 +96,17 @@ impl AtomicFrameBuffer { Ok(()) } - // walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates - fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec { - let mut meta_tiles: MetaTileVec = heapless::Vec::new(); + // Checks if a full draw would be faster than individual tile batches + fn should_full_draw(&self) -> bool { + let threshold_pixels = SIZE * 80 / 100; + let mut dirty_pixels = 0; - for ty in 0..tiles_y { - let mut tx = 0; - while tx < tiles_x { - let idx = ty * tiles_x + tx; - if !unsafe { DIRTY_TILES.get()[idx].load(Ordering::Acquire) } { - tx += 1; - continue; - } - - // Start meta-tile at this tile - let mut width_tiles = 1; - let height_tiles = 1; - - // Grow horizontally, but keep under MAX_TILES_PER_METATILE - while tx + width_tiles < tiles_x - && unsafe { - DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Relaxed) - } - && (width_tiles + height_tiles) <= MAX_META_TILES - { - width_tiles += 1; - } - - // TODO: for simplicity, skipped vertical growth - - for x_off in 0..width_tiles { - unsafe { - DIRTY_TILES.get()[ty * tiles_x + tx + x_off] - .store(false, Ordering::Release); - }; - } - - // new meta-tile pos - let rect = Rectangle::new( - Point::new((tx * TILE_SIZE) as i32, (ty * TILE_SIZE) as i32), - Size::new( - (width_tiles * TILE_SIZE) as u32, - (height_tiles * TILE_SIZE) as u32, - ), - ); - - if meta_tiles.push(rect).is_err() { - return meta_tiles; - }; - - tx += width_tiles; + self.dirty_tiles.iter().any(|tile| { + if tile.load(Ordering::Acquire) { + dirty_pixels += TILE_SIZE * TILE_SIZE; } - } - - meta_tiles + dirty_pixels >= threshold_pixels + }) } /// Sends the entire framebuffer to the display @@ -171,21 +125,105 @@ impl AtomicFrameBuffer { 0, self.size().width as u16 - 1, self.size().height as u16 - 1, - unsafe { &BUFFER }, + &self.fb[..], ) .await?; + for tile in self.dirty_tiles.iter() { + tile.store(false, Ordering::Release); + } + + #[cfg(feature = "fps")] unsafe { - for tile in DIRTY_TILES.get_mut().iter() { - tile.store(false, Ordering::Release); - } - }; + crate::display::FPS_COUNTER.measure() + } Ok(()) } - /// Sends only dirty tiles (16x16px) in batches to the display - pub async fn partial_draw_batched( + // used when doing a full screen refresh fps must be drawn into fb + // unfortunately it is not garenteed to not be drawn over before + // being pushed to the display + #[cfg(feature = "fps")] + pub fn draw_fps_into_fb(&mut self) { + unsafe { + let canvas = &FPS_CANVAS.canvas; + + for y in 0..FPS_CANVAS_HEIGHT { + let fb_y = FPS_CANVAS_Y + y; + let fb_row_start = fb_y * SCREEN_WIDTH + FPS_CANVAS_X; + let canvas_row_start = y * FPS_CANVAS_WIDTH; + + self.fb[fb_row_start..fb_row_start + FPS_CANVAS_WIDTH].copy_from_slice( + &canvas[canvas_row_start..canvas_row_start + FPS_CANVAS_WIDTH], + ); + } + } + } + + // copy N tiles horizontally to the right into batch tile buf + fn append_tiles_to_batch( + &mut self, + tile_x: u16, + tile_y: u16, + total_tiles: u16, // number of tiles being written to buf + ) { + debug_assert!(total_tiles as usize <= NUM_TILE_COLS); + for batch_row_num in 0..TILE_SIZE { + let batch_row_offset = batch_row_num * total_tiles as usize * TILE_SIZE; + let batch_row = &mut self.batch_tile_buf + [batch_row_offset..batch_row_offset + (total_tiles as usize * TILE_SIZE)]; + + let fb_row_offset = (tile_y as usize * TILE_SIZE + batch_row_num) * SCREEN_WIDTH + + tile_x as usize * TILE_SIZE; + let fb_row = + &self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)]; + + batch_row.copy_from_slice(fb_row); + + // override fps pixel region with fps + // avoids writing to fps, and having it overridden before draw + #[cfg(feature = "fps")] + { + let global_y = tile_y as usize * TILE_SIZE + batch_row_num; + + if global_y >= FPS_CANVAS_Y && global_y < FPS_CANVAS_Y + FPS_CANVAS_HEIGHT { + let start_x = tile_x as usize * TILE_SIZE; + let end_x = start_x + (total_tiles as usize * TILE_SIZE); + + // horizontal overlap check + let fps_x0 = FPS_CANVAS_X; + let fps_x1 = FPS_CANVAS_X + FPS_CANVAS_WIDTH; + + let x0 = start_x.max(fps_x0); + let x1 = end_x.min(fps_x1); + + if x1 > x0 { + let row_in_fps = global_y - FPS_CANVAS_Y; + let fps_off = row_in_fps + .checked_mul(FPS_CANVAS_WIDTH) + .and_then(|v| v.checked_add(x0 - fps_x0)); + let batch_off = x0 - start_x; + let len = x1 - x0; + + if let Some(fps_off) = fps_off { + let fps_len_ok = fps_off + len <= unsafe { FPS_CANVAS.canvas.len() }; + let batch_len_ok = batch_off + len <= batch_row.len(); + + if fps_len_ok && batch_len_ok { + batch_row[batch_off..batch_off + len].copy_from_slice(unsafe { + &FPS_CANVAS.canvas[fps_off..fps_off + len] + }); + } + } + } + } + } + } + } + + // Pushes tiles to the display in batches to avoid full frame pushes (unless needed) + pub async fn partial_draw( &mut self, display: &mut ST7365P, ) -> Result<(), ()> @@ -195,65 +233,82 @@ impl AtomicFrameBuffer { RST: OutputPin, DELAY: DelayNs, { - if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { - let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; - let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + if self.should_full_draw() { + #[cfg(feature = "fps")] + self.draw_fps_into_fb(); + return self.draw(display).await; + } - let meta_tiles = self.find_meta_tiles(tiles_x, tiles_y); + #[cfg(feature = "fps")] + { + let fps_tile_x = FPS_CANVAS_X / TILE_SIZE; + let fps_tile_y = FPS_CANVAS_Y / TILE_SIZE; + let fps_tile_w = (FPS_CANVAS_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let fps_tile_h = (FPS_CANVAS_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; - // buffer for copying meta tiles before sending to display - let mut pixel_buffer: heapless::Vec = - Vec::new(); - - for rect in meta_tiles { - let rect_width = rect.size.width as usize; - let rect_height = rect.size.height as usize; - let rect_x = rect.top_left.x as usize; - let rect_y = rect.top_left.y as usize; - - pixel_buffer.clear(); - - for row in 0..rect_height { - let y = rect_y + row; - let start = y * SCREEN_WIDTH + rect_x; - let end = start + rect_width; - - // Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS - pixel_buffer - .extend_from_slice(unsafe { &BUFFER[start..end] }) - .unwrap(); - } - - display - .set_pixels_buffered( - rect_x as u16, - rect_y as u16, - (rect_x + rect_width - 1) as u16, - (rect_y + rect_height - 1) as u16, - &pixel_buffer, - ) - .await?; - - // walk the meta-tile and set as clean - let start_tx = rect_x / TILE_SIZE; - let start_ty = rect_y / TILE_SIZE; - let end_tx = (rect_x + rect_width - 1) / TILE_SIZE; - let end_ty = (rect_y + rect_height - 1) / TILE_SIZE; - - for ty in start_ty..=end_ty { - for tx in start_tx..=end_tx { - let tile_idx = ty * tiles_x + tx; - unsafe { DIRTY_TILES.get_mut()[tile_idx].store(false, Ordering::Release) }; - } + for ty in fps_tile_y..fps_tile_y + fps_tile_h { + for tx in fps_tile_x..fps_tile_x + fps_tile_w { + self.dirty_tiles[ty * NUM_TILE_COLS + tx].store(true, Ordering::Release); } } } + for tile_row in 0..NUM_TILE_ROWS { + let row_start_idx = tile_row * NUM_TILE_COLS; + let mut col = 0; + + while col < NUM_TILE_COLS { + // Check for dirty tile + if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) { + let run_start = col; + let mut run_len = 1; + + // Extend run while contiguous dirty tiles and within MAX_BATCH_TILES + while col + 1 < NUM_TILE_COLS + && self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire) + && run_len < MAX_BATCH_TILES + { + col += 1; + run_len += 1; + } + + // Copy the whole horizontal run into the batch buffer in one call + let tile_x = run_start; + let tile_y = tile_row; + self.append_tiles_to_batch(tile_x as u16, tile_y as u16, run_len as u16); + + // Compute coordinates for display write + let start_x = tile_x * TILE_SIZE; + let end_x = start_x + run_len * TILE_SIZE - 1; + let start_y = tile_y * TILE_SIZE; + let end_y = start_y + TILE_SIZE - 1; + + // Send batch to display + display + .set_pixels_buffered( + start_x as u16, + start_y as u16, + end_x as u16, + end_y as u16, + &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], + ) + .await?; + } + + col += 1; + } + } + + #[cfg(feature = "fps")] + unsafe { + crate::display::FPS_COUNTER.measure() + } + Ok(()) } } -impl DrawTarget for AtomicFrameBuffer { +impl<'a> DrawTarget for AtomicFrameBuffer<'a> { type Error = (); type Color = Rgb565; @@ -262,17 +317,20 @@ impl DrawTarget for AtomicFrameBuffer { I: IntoIterator>, { let mut dirty_rect: Option = None; + let mut changed = false; for Pixel(coord, color) in pixels { if coord.x >= 0 && coord.y >= 0 { - let x = coord.x as i32; - let y = coord.y as i32; + let x = coord.x; + let y = coord.y; if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { - unsafe { - BUFFER[(y as usize) * SCREEN_WIDTH + (x as usize)] = - RawU16::from(color).into_inner() - }; + let idx = (y as usize) * SCREEN_WIDTH + (x as usize); + let raw_color = RawU16::from(color).into_inner(); + if self.fb[idx] != raw_color { + self.fb[idx] = raw_color; + changed = true; + } if let Some(ref mut rect) = dirty_rect { rect.top_left.x = rect.top_left.x.min(x); @@ -288,8 +346,10 @@ impl DrawTarget for AtomicFrameBuffer { } } - if let Some(rect) = dirty_rect { - self.mark_tiles_dirty(rect); + if changed { + if let Some(rect) = dirty_rect { + self.mark_tiles_dirty(rect); + } } Ok(()) @@ -307,6 +367,7 @@ impl DrawTarget for AtomicFrameBuffer { let area_width = area.size.width; let area_height = area.size.height; let mut colors = colors.into_iter(); + let mut changed = false; for y in 0..area_height { for x in 0..area_width { @@ -314,11 +375,12 @@ impl DrawTarget for AtomicFrameBuffer { if drawable_area.contains(p) { if let Some(color) = colors.next() { - self.set_pixel( - p.x as u16, - p.y as u16, - RawU16::from(color).into_inner(), - )?; + let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize); + let raw_color = RawU16::from(color).into_inner(); + if self.fb[idx] != raw_color { + self.fb[idx] = raw_color; + changed = true; + } } else { break; } @@ -329,7 +391,9 @@ impl DrawTarget for AtomicFrameBuffer { } } - self.mark_tiles_dirty(*area); + if changed { + self.mark_tiles_dirty(*area); + } } Ok(()) @@ -343,7 +407,7 @@ impl DrawTarget for AtomicFrameBuffer { } fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { - self.set_pixels_buffered( + self.set_pixels( 0, 0, self.size().width as u16 - 1, @@ -352,7 +416,7 @@ impl DrawTarget for AtomicFrameBuffer { .take((self.size().width * self.size().height) as usize), )?; - for tile in unsafe { DIRTY_TILES.get_mut() }.iter() { + for tile in self.dirty_tiles.iter() { tile.store(true, Ordering::Release); } @@ -360,8 +424,151 @@ impl DrawTarget for AtomicFrameBuffer { } } -impl OriginDimensions for AtomicFrameBuffer { +impl<'a> OriginDimensions for AtomicFrameBuffer<'a> { fn size(&self) -> Size { Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) } } + +#[cfg(feature = "fps")] +pub mod fps { + use crate::display::SCREEN_WIDTH; + use core::fmt::Write; + use embassy_time::{Duration, Instant}; + use embedded_graphics::{ + Drawable, Pixel, + draw_target::DrawTarget, + geometry::Point, + mono_font::{MonoTextStyle, ascii::FONT_8X13}, + pixelcolor::Rgb565, + prelude::{IntoStorage, OriginDimensions, RgbColor, Size}, + text::{Alignment, Text}, + }; + + pub static mut FPS_COUNTER: FpsCounter = FpsCounter::new(); + pub static mut FPS_CANVAS: FpsCanvas = FpsCanvas::new(); + + // "FPS: 120" = 8 len + const FPS_LEN: usize = 8; + pub const FPS_CANVAS_WIDTH: usize = (FONT_8X13.character_size.width + 4) as usize * FPS_LEN; + pub const FPS_CANVAS_HEIGHT: usize = FONT_8X13.character_size.height as usize; + + // puts canvas in the top right of the display + // top left point of canvas + pub const FPS_CANVAS_X: usize = SCREEN_WIDTH - FPS_CANVAS_WIDTH; + pub const FPS_CANVAS_Y: usize = 0; + + pub struct FpsCanvas { + pub canvas: [u16; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + } + + impl FpsCanvas { + const fn new() -> Self { + Self { + canvas: [0; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH], + } + } + + fn clear(&mut self) { + for p in &mut self.canvas { + *p = 0; + } + } + + pub async fn draw_fps(&mut self) { + let mut buf: heapless::String = heapless::String::new(); + let fps = unsafe { FPS_COUNTER.smoothed }; + let _ = write!(buf, "FPS: {}", fps as u8); + + self.clear(); + let text_style = MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE); + Text::with_alignment( + buf.as_str(), + Point::new( + FPS_CANVAS_WIDTH as i32 / 2, + (FPS_CANVAS_HEIGHT as i32 + 8) / 2, + ), + text_style, + Alignment::Center, + ) + .draw(self) + .unwrap(); + } + } + + impl DrawTarget for FpsCanvas { + type Error = (); + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(point, color) in pixels { + if point.x < 0 + || point.x >= FPS_CANVAS_WIDTH as i32 + || point.y < 0 + || point.y >= FPS_CANVAS_HEIGHT as i32 + { + continue; + } + + let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize; + self.canvas[index] = color.into_storage(); + } + Ok(()) + } + } + + impl OriginDimensions for FpsCanvas { + fn size(&self) -> Size { + Size::new(FPS_CANVAS_WIDTH as u32, FPS_CANVAS_HEIGHT as u32) + } + } + + pub struct FpsCounter { + last_frame: Option, + smoothed: f32, + last_draw: Option, + } + + impl FpsCounter { + pub const fn new() -> Self { + Self { + last_frame: None, + smoothed: 0.0, + last_draw: None, + } + } + + // Is called once per frame or partial frame to update FPS + pub fn measure(&mut self) { + let now = Instant::now(); + + if let Some(last) = self.last_frame { + let dt_us = (now - last).as_micros() as f32; + if dt_us > 0.0 { + let current = 1_000_000.0 / dt_us; + self.smoothed = if self.smoothed == 0.0 { + current + } else { + 0.9 * self.smoothed + 0.1 * current + }; + } + } + + self.last_frame = Some(now); + } + + pub fn should_draw(&mut self) -> bool { + let now = Instant::now(); + match self.last_draw { + Some(last) if now - last < Duration::from_millis(200) => false, + _ => { + self.last_draw = Some(now); + true + } + } + } + } +} diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs new file mode 100644 index 0000000..2b1de30 --- /dev/null +++ b/kernel/src/heap.rs @@ -0,0 +1,95 @@ +// This whole file was taken from: +// https://github.com/wezterm/picocalc-wezterm/blob/main/src/heap.rs + +use core::alloc::{GlobalAlloc, Layout}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use embedded_alloc::LlffHeap as Heap; + +pub static mut HEAP: PsramHeap = PsramHeap::empty(); + +struct Region { + start: AtomicUsize, + size: AtomicUsize, +} + +impl Region { + const fn default() -> Self { + Self { + start: AtomicUsize::new(0), + size: AtomicUsize::new(0), + } + } + + fn contains(&self, address: usize) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.start.load(Ordering::Relaxed); + (start..start + end).contains(&address) + } + + fn new(start: usize, size: usize) -> Self { + Self { + start: AtomicUsize::new(start), + size: AtomicUsize::new(size), + } + } +} + +/// FIXME: PSRAM-allocated memory isn't compatible with +/// CAS atomics, so we might need a bit of a think about this! +pub struct PsramHeap { + heap: Heap, + region: Region, +} + +impl PsramHeap { + pub const fn empty() -> Self { + Self { + heap: Heap::empty(), + region: Region::default(), + } + } + + unsafe fn add_psram(&self, region: Region) { + let start = region.start.load(Ordering::SeqCst); + let size = region.size.load(Ordering::SeqCst); + unsafe { + self.heap.init(start, size); + } + self.region.start.store(start, Ordering::SeqCst); + self.region.size.store(size, Ordering::SeqCst); + } + + pub fn used(&self) -> usize { + self.heap.used() + } + + pub fn free(&self) -> usize { + self.heap.free() + } +} + +unsafe impl GlobalAlloc for PsramHeap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + let ptr = self.heap.alloc(layout); + if !ptr.is_null() { + return ptr; + } else { + panic!("HEAP FULL"); + } + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + let ptr_usize = ptr as usize; + if self.region.contains(ptr_usize) { + self.heap.dealloc(ptr, layout); + } + } + } +} + +pub fn init_qmi_psram_heap(size: u32) { + unsafe { HEAP.add_psram(Region::new(0x11000000, size as usize)) } +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index aedc590..107e332 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,6 +3,8 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![allow(static_mut_refs)] +#![feature(allocator_api)] +#![feature(slice_ptr_get)] extern crate alloc; @@ -18,8 +20,18 @@ mod ui; mod usb; mod utils; +#[cfg(feature = "psram")] +#[allow(unused)] +mod heap; +#[cfg(feature = "psram")] +#[allow(unused)] +mod psram; + +#[cfg(feature = "psram")] +use crate::{heap::HEAP, heap::init_qmi_psram_heap, psram::init_psram, psram::init_psram_qmi}; + use crate::{ - abi::KEY_CACHE, + abi::{KEY_CACHE, MS_SINCE_LAUNCH}, audio::audio_handler, display::{FRAMEBUFFER, display_handler, init_display}, peripherals::{ @@ -32,38 +44,39 @@ use crate::{ }; use abi_sys::EntryFn; use bumpalo::Bump; -use embedded_graphics::{ - pixelcolor::Rgb565, - prelude::{DrawTarget, RgbColor}, -}; - -use {defmt_rtt as _, panic_probe as _}; - use core::sync::atomic::{AtomicBool, Ordering}; -use defmt::unwrap; use embassy_executor::{Executor, Spawner}; use embassy_futures::{join::join, select::select}; use embassy_rp::{ Peri, + clocks::ClockConfig, + config::Config, gpio::{Input, Level, Output, Pull}, i2c::{self, I2c}, multicore::{Stack, spawn_core1}, peripherals::{ - DMA_CH0, DMA_CH1, DMA_CH3, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, - PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, PIN_26, PIN_27, PIO0, SPI0, SPI1, USB, + DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, + PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, + PIN_26, PIN_27, PIO0, SPI0, SPI1, USB, WATCHDOG, }, pio::{self, Common, Pio, StateMachine}, spi::{self, Spi}, usb as embassy_rp_usb, + watchdog::{ResetReason, Watchdog}, }; use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal, }; -use embassy_time::{Delay, Timer}; +use embassy_time::{Delay, Duration, Instant, Ticker, Timer}; +use embedded_graphics::{ + pixelcolor::Rgb565, + prelude::{DrawTarget, RgbColor}, +}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::SdCard as SdmmcSdCard; use static_cell::StaticCell; use talc::*; +use {defmt_rtt as _, panic_probe as _}; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; @@ -75,26 +88,55 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 200 * 1024] = [0; 200 * 1024]; +#[cfg(not(feature = "pimoroni2w"))] +static mut ARENA: [u8; 250 * 1024] = [0; 250 * 1024]; +#[cfg(feature = "pimoroni2w")] +static mut ARENA: [u8; 400 * 1024] = [0; 400 * 1024]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) .lock(); +#[embassy_executor::task] +async fn watchdog_task(mut watchdog: Watchdog) { + if let Some(reason) = watchdog.reset_reason() { + let _reason = match reason { + ResetReason::Forced => "forced", + ResetReason::TimedOut => "timed out", + }; + #[cfg(feature = "debug")] + defmt::error!("Watchdog reset reason: {}", _reason); + } + + watchdog.start(Duration::from_secs(3)); + + let mut ticker = Ticker::every(Duration::from_secs(2)); + loop { + watchdog.feed(); + ticker.next().await; + } +} + static ENABLE_UI: AtomicBool = AtomicBool::new(true); static UI_CHANGE: Signal = Signal::new(); #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); + let p = if cfg!(feature = "overclock") { + let clocks = ClockConfig::system_freq(300_000_000).unwrap(); + let config = Config::new(clocks); + embassy_rp::init(config) + } else { + embassy_rp::init(Default::default()) + }; spawn_core1( p.CORE1, unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, move || { let executor1 = EXECUTOR1.init(Executor::new()); - executor1.run(|spawner| unwrap!(spawner.spawn(userland_task()))); + executor1.run(|spawner| spawner.spawn(userland_task()).unwrap()); }, ); @@ -129,6 +171,15 @@ async fn main(_spawner: Spawner) { cs: p.PIN_17, det: p.PIN_22, }; + // let psram = Psram { + // pio: p.PIO0, + // sclk: p.PIN_21, + // mosi: p.PIN_2, + // miso: p.PIN_3, + // cs: p.PIN_20, + // dma1: p.DMA_CH3, + // dma2: p.DMA_CH4, + // }; let mcu = Mcu { i2c: p.I2C1, clk: p.PIN_7, @@ -136,7 +187,11 @@ async fn main(_spawner: Spawner) { }; let executor0 = EXECUTOR0.init(Executor::new()); executor0.run(|spawner| { - unwrap!(spawner.spawn(kernel_task(spawner, display, audio, sd, mcu, p.USB))) + spawner + .spawn(kernel_task( + spawner, p.WATCHDOG, display, audio, sd, mcu, p.USB, + )) + .unwrap() }); } @@ -160,6 +215,8 @@ async fn userland_task() { MSC_SHUTDOWN.signal(()); } + unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) }; + #[cfg(feature = "defmt")] defmt::info!("Executing Binary"); entry(); @@ -167,7 +224,7 @@ async fn userland_task() { { ENABLE_UI.store(true, Ordering::Release); UI_CHANGE.signal(()); - unsafe { FRAMEBUFFER.clear(Rgb565::BLACK).unwrap() }; + unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() }; let mut selections = SELECTIONS.lock().await; selections.set_changed(true); @@ -202,6 +259,16 @@ struct Sd { cs: Peri<'static, PIN_17>, det: Peri<'static, PIN_22>, } +#[allow(dead_code)] +struct Psram { + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, +} struct Mcu { i2c: Peri<'static, I2C1>, clk: Peri<'static, PIN_7>, @@ -218,7 +285,7 @@ async fn setup_mcu(mcu: Mcu) { async fn setup_display(display: Display, spawner: Spawner) { let mut config = spi::Config::default(); - config.frequency = 16_000_000; + config.frequency = 192_000_000; let spi = Spi::new( display.spi, display.clk, @@ -232,6 +299,39 @@ async fn setup_display(display: Display, spawner: Spawner) { spawner.spawn(display_handler(display)).unwrap(); } +// psram is kind of useless on the pico calc +// ive opted to use the pimoroni with on onboard xip psram instead +// async fn setup_psram(psram: Psram) { +// let psram = init_psram( +// psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, +// ) +// .await; + +// #[cfg(feature = "defmt")] +// defmt::info!("psram size: {}", psram.size); + +// if psram.size == 0 { +// #[cfg(feature = "defmt")] +// defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); +// } +// } + +#[cfg(feature = "psram")] +async fn setup_qmi_psram() { + Timer::after_millis(250).await; + let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); + #[cfg(feature = "debug")] + defmt::info!("size: {}", psram_qmi_size); + Timer::after_millis(100).await; + + if psram_qmi_size > 0 { + init_qmi_psram_heap(psram_qmi_size); + return; + } else { + panic!("qmi psram not initialized"); + } +} + async fn setup_sd(sd: Sd) { let mut config = spi::Config::default(); config.frequency = 400_000; @@ -250,13 +350,32 @@ async fn setup_sd(sd: Sd) { #[embassy_executor::task] async fn kernel_task( spawner: Spawner, + watchdog: Peri<'static, WATCHDOG>, display: Display, audio: Audio, sd: Sd, + // _psram: Psram, mcu: Mcu, usb: Peri<'static, USB>, ) { + spawner + .spawn(watchdog_task(Watchdog::new(watchdog))) + .unwrap(); + + #[cfg(feature = "debug")] + defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq()); + setup_mcu(mcu).await; + + #[cfg(feature = "defmt")] + defmt::info!("setting up psram"); + Timer::after_millis(100).await; + + // setup_psram(psram).await; + #[cfg(feature = "psram")] + setup_qmi_psram().await; + + Timer::after_millis(100).await; setup_display(display, spawner).await; setup_sd(sd).await; @@ -281,7 +400,8 @@ async fn prog_search_handler() { let mut guard = SDCARD.get().lock().await; let sd = guard.as_mut().unwrap(); - let files = sd.list_files_by_extension(".bin").unwrap(); + let mut files = sd.list_files_by_extension(".bin").unwrap(); + files.sort(); let mut select = SELECTIONS.lock().await; if *select.selections() != files { @@ -296,10 +416,8 @@ async fn prog_search_handler() { async fn key_handler() { loop { if let Some(event) = read_keyboard_fifo().await { - if let KeyState::Pressed = event.state { - unsafe { - let _ = KEY_CACHE.enqueue(event); - } + unsafe { + let _ = KEY_CACHE.enqueue(event); } } Timer::after_millis(50).await; diff --git a/kernel/src/peripherals/keyboard.rs b/kernel/src/peripherals/keyboard.rs index a9ef605..28b65e7 100644 --- a/kernel/src/peripherals/keyboard.rs +++ b/kernel/src/peripherals/keyboard.rs @@ -1,5 +1,5 @@ use crate::peripherals::PERIPHERAL_BUS; -pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +pub use abi_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; const REG_ID_KEY: u8 = 0x04; const REG_ID_FIF: u8 = 0x09; diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs new file mode 100644 index 0000000..91d447a --- /dev/null +++ b/kernel/src/psram.rs @@ -0,0 +1,618 @@ +// This whole file was taken from +// +// +use crate::Irqs; +use embassy_futures::yield_now; +use embassy_rp::Peri; +use embassy_rp::clocks::clk_peri_freq; +use embassy_rp::gpio::{Drive, SlewRate}; +use embassy_rp::peripherals::{DMA_CH3, DMA_CH4, PIN_2, PIN_3, PIN_20, PIN_21, PIO0}; +use embassy_rp::pio::program::pio_asm; +use embassy_rp::pio::{Config, Direction, Pio, ShiftDirection}; +use embassy_rp::pio_programs::clock_divider::calculate_pio_clock_divider; +use embassy_time::{Duration, Instant, Timer}; + +// The physical connections in the picocalc schematic are: +// LABEL PICO ESP-PSRAM64H +// RAM_CS - PIN_20 CE (pulled up to 3v3 via 10kOhm) +// RAM_SCK - PIN_21 SCLK +// RAM_TX - PIN_2 SI/SIO0 +// RAM_RX - PIN_3 SO/SIO1 +// RAM_IO2 - PIN_4 SIO2 (QPI Mode) +// RAM_IO3 - PIN_5 SIO3 (QPI Mode) + +#[allow(unused)] +const PSRAM_CMD_QUAD_END: u8 = 0xf5; +#[allow(unused)] +const PSRAM_CMD_QUAD_ENABLE: u8 = 0x35; +#[allow(unused)] +const PSRAM_CMD_READ_ID: u8 = 0x9F; +const PSRAM_CMD_RSTEN: u8 = 0x66; +const PSRAM_CMD_RST: u8 = 0x99; +const PSRAM_CMD_WRITE: u8 = 0x02; +const PSRAM_CMD_FAST_READ: u8 = 0x0B; +#[allow(unused)] +const PSRAM_CMD_QUAD_READ: u8 = 0xEB; +#[allow(unused)] +const PSRAM_CMD_QUAD_WRITE: u8 = 0x38; +#[allow(unused)] +const PSRAM_CMD_NOOP: u8 = 0xFF; +#[allow(unused)] +const PSRAM_KNOWN_GOOD_DIE_PASS: u8 = 0x5d; + +const MAX_PSRAM_FREQ: u32 = 133_000_000; + +pub struct PsRam { + sm: embassy_rp::pio::StateMachine<'static, PIO0, 0>, + tx_ch: Peri<'static, DMA_CH3>, + rx_ch: Peri<'static, DMA_CH4>, + pub size: u32, +} + +impl PsRam { + pub async fn send_command(&mut self, cmd: &[u8], out: &mut [u8]) { + if out.is_empty() { + self.sm + .tx() + .dma_push(self.tx_ch.reborrow(), cmd, false) + .await; + } else { + let (rx, tx) = self.sm.rx_tx(); + tx.dma_push(self.tx_ch.reborrow(), cmd, false).await; + rx.dma_pull(self.rx_ch.reborrow(), out, false).await; + } + } + + pub async fn write(&mut self, mut addr: u32, mut data: &[u8]) { + // I haven't seen this work reliably over 24 bytes + const MAX_CHUNK: usize = 24; + while data.len() > 0 { + let to_write = data.len().min(MAX_CHUNK); + //defmt::info!("writing {to_write} @ {addr}"); + + #[rustfmt::skip] + let mut to_send = [ + 32 + (to_write as u8 * 8), // write address + data + 0, // read 0 bits + PSRAM_CMD_WRITE, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + // This sequence must be MAX_CHUNK in length + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + for (src, dst) in data.iter().zip(to_send.iter_mut().skip(6)) { + *dst = *src; + } + + self.send_command(&to_send[0..6 + to_write], &mut []).await; + addr += to_write as u32; + data = &data[to_write..]; + } + } + + pub async fn read_id(&mut self) -> [u8; 3] { + let mut id = [0u8; 3]; + #[rustfmt::skip] + self.send_command( + &[ + 32, // write 32 bits + 3 * 8, // read 8 bytes = 64 bits + PSRAM_CMD_READ_ID, + // don't care: 24-bit "address" + 0, 0, 0, + ], + &mut id, + ) + .await; + id + } + + pub async fn read(&mut self, mut addr: u32, mut out: &mut [u8]) { + // Cannot get reliable reads above 4 bytes at a time. + // out[4] will always have a bit error + const MAX_CHUNK: usize = 4; + while out.len() > 0 { + let to_read = out.len().min(MAX_CHUNK); + //defmt::info!("reading {to_read} @ {addr}"); + self.send_command( + &[ + 40, // write 40 bits + to_read as u8 * 8, // read n bytes + PSRAM_CMD_FAST_READ, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + 0, // 8 cycle delay by sending 8 bits of don't care data + ], + &mut out[0..to_read], + ) + .await; + addr += to_read as u32; + out = &mut out[to_read..]; + } + } + + #[allow(unused)] + pub async fn write8(&mut self, addr: u32, data: u8) { + //defmt::info!("write8 addr {addr} <- {data:x}"); + self.send_command( + &[ + 40, // write 40 bits + 0, // read 0 bits + PSRAM_CMD_WRITE, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + data, + ], + &mut [], + ) + .await; + } + + #[allow(unused)] + pub async fn read8(&mut self, addr: u32) -> u8 { + let mut buf = [0u8]; + self.send_command( + &[ + 40, // write 40 bits + 8, // read 8 bits + PSRAM_CMD_FAST_READ, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + 0, // 8 cycle delay + ], + &mut buf, + ) + .await; + buf[0] + } +} + +pub async fn init_psram( + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, +) -> PsRam { + let mut pio = Pio::new(pio, Irqs); + + let divider = calculate_pio_clock_divider(MAX_PSRAM_FREQ); + + // This pio program was taken from + // + // which is Copyright © 2023 Ian Scott, reproduced here under the MIT license + + let p = pio_asm!( + r#" +.side_set 2 ; sideset bit 1 is SCK, bit 0 is CS +begin: + out x, 8 side 0b01 ; x = number of bits to output. CS deasserted + out y, 8 side 0b01 ; y = number of bits to input + jmp x--, writeloop side 0b01 ; Pre-decement x by 1 so loop has correct number of iterations +writeloop: + out pins, 1 side 0b00 ; Write value on pin, lower clock. CS asserted + jmp x--, writeloop side 0b10 ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write + jmp !y, done side 0b00 ; If this is a write-only operation, jump back to beginning + nop side 0b10 ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing + jmp readloop_mid side 0b00 ; Jump to middle of readloop to decrement y and get right clock phase +readloop: + in pins, 1 side 0b00 ; Read value on pin, lower clock. Datasheet says to read on falling edge > 83MHz +readloop_mid: + jmp y--, readloop side 0b10 ; Raise clock. Loop if we have more to read +done: + nop side 0b11 ; CS deasserted + "# + ); + let prog = pio.common.load_program(&p.program); + + let mut cfg = Config::default(); + + let mut cs = pio.common.make_pio_pin(cs); + let mut sclk = pio.common.make_pio_pin(sclk); + let mut mosi = pio.common.make_pio_pin(mosi); + let mut miso = pio.common.make_pio_pin(miso); + + sclk.set_slew_rate(SlewRate::Fast); + mosi.set_slew_rate(SlewRate::Fast); + + cs.set_drive_strength(Drive::_4mA); + sclk.set_drive_strength(Drive::_4mA); + mosi.set_drive_strength(Drive::_4mA); + miso.set_drive_strength(Drive::_4mA); + + cfg.use_program(&prog, &[&cs, &sclk]); + cfg.set_out_pins(&[&mosi]); + cfg.set_in_pins(&[&miso]); + + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + cfg.shift_out.threshold = 8; + + cfg.shift_in = cfg.shift_out; + cfg.clock_divider = divider; + + let mut sm = pio.sm0; + sm.restart(); + sm.set_pin_dirs(Direction::Out, &[&cs, &sclk]); + sm.set_pin_dirs(Direction::Out, &[&mosi]); + sm.set_pin_dirs(Direction::In, &[&miso]); + miso.set_input_sync_bypass(true); + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut psram = PsRam { + sm, + tx_ch: dma1, + rx_ch: dma2, + size: 0, + }; + + // Issue a reset command + psram.send_command(&[8, 0, PSRAM_CMD_RSTEN], &mut []).await; + Timer::after(Duration::from_micros(50)).await; + psram.send_command(&[8, 0, PSRAM_CMD_RST], &mut []).await; + Timer::after(Duration::from_micros(100)).await; + + #[cfg(feature = "defmt")] + defmt::info!("Verifying 1 byte write and read..."); + for i in 0..10u8 { + psram.write8(i as u32, i).await; + } + for i in 0..10u32 { + let n = psram.read8(i as u32).await; + if n as u32 != i {} + } + #[cfg(feature = "defmt")] + defmt::info!("testing read again @ 0"); + let mut got = [0u8; 8]; + psram.read(0, &mut got).await; + const EXPECT: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; + if got != EXPECT { + #[cfg(feature = "defmt")] + defmt::warn!("Got Read error"); + } + + const DEADBEEF: &[u8] = &[0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + #[cfg(feature = "defmt")] + defmt::info!("testing write of deadbeef at 0"); + psram.write(0, DEADBEEF).await; + + #[cfg(feature = "defmt")] + defmt::info!("testing read of deadbeef from 0"); + psram.read(0, &mut got).await; + if got != DEADBEEF { + for addr in 0..DEADBEEF.len() { + let bad = got[addr]; + if bad != DEADBEEF[addr] { + let x = psram.read8(addr as u32).await; + #[cfg(feature = "defmt")] + defmt::info!("read addr: {}, got: {:X}", addr, x); + } + } + } + + const TEST_STRING: &[u8] = b"hello there, this is a test, how is it?"; + psram.write(16, TEST_STRING).await; + + let mut buffer = [0u8; 42]; + psram.read(16, &mut buffer).await; + + let got = &buffer[0..TEST_STRING.len()]; + + if got != TEST_STRING {} + + #[cfg(feature = "defmt")] + defmt::info!("PSRAM test complete"); + + let id = psram.read_id().await; + #[cfg(feature = "defmt")] + defmt::info!("psram id: {}", id); + // id: [d, 5d, 53, 15, 49, e3, 7c, 7b] + // id[0] -- manufacturer id + // id[1] -- "known good die" status + if id[1] == PSRAM_KNOWN_GOOD_DIE_PASS { + // See + // for information on deciding the size of ESP PSRAM chips, + // such as the one used in the picocalc + let size = match (id[2] >> 5) & 0x7 { + 0 => 16, + 1 => 32, + 2 => 64, + _ => 0, + }; + psram.size = size * 1024 * 1024 / 8; + } + + psram +} + +#[allow(unused)] +pub async fn test_psram(psram: &mut PsRam) -> bool { + const REPORT_CHUNK: u32 = 256 * 1024; + const BLOCK_SIZE: usize = 8; + let limit = psram.size; //.min(4 * 1024 * 1024); + + let start = Instant::now(); + + fn expect(addr: u32) -> [u8; BLOCK_SIZE] { + [ + !((addr >> 24 & 0xff) as u8), + !((addr >> 16 & 0xff) as u8), + !((addr >> 8 & 0xff) as u8), + !((addr & 0xff) as u8), + ((addr >> 24 & 0xff) as u8), + ((addr >> 16 & 0xff) as u8), + ((addr >> 8 & 0xff) as u8), + ((addr & 0xff) as u8), + ] + } + + for i in 0..limit / BLOCK_SIZE as u32 { + let addr = i * BLOCK_SIZE as u32; + let data = expect(addr); + psram.write(addr, &data).await; + if addr > 0 && addr % REPORT_CHUNK == 0 { + if start.elapsed() > Duration::from_secs(5) {} + } + // Yield so that the watchdog doesn't kick in + yield_now().await; + } + let writes_took = start.elapsed(); + + #[cfg(feature = "defmt")] + defmt::info!("Starting reads..."); + Timer::after(Duration::from_millis(200)).await; + + let start = Instant::now(); + let mut bad_count = 0; + let mut data = [0u8; BLOCK_SIZE]; + for i in 0..limit / BLOCK_SIZE as u32 { + let addr = i * BLOCK_SIZE as u32; + let expect = expect(addr); + psram.read(addr, &mut data).await; + if addr == 0 { + Timer::after(Duration::from_millis(200)).await; + } + if data != expect { + bad_count += 1; + if bad_count < 50 {} + } + if addr > 0 && addr % REPORT_CHUNK == 0 { + if start.elapsed() > Duration::from_secs(5) {} + } + + // Yield so that the watchdog doesn't kick in + yield_now().await; + } + let reads_took = start.elapsed(); + + bad_count == 0 +} + +// The origin of the code in this file is: +// +// which is MIT/Apache-2 licensed. +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn detect_psram_qmi(qmi: &embassy_rp::pac::qmi::Qmi) -> u32 { + const GPIO_FUNC_XIP_CS1: u8 = 9; + const XIP_CS_PIN: usize = 47; + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(true); + }); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_ie(true); + w.set_od(false); + }); + embassy_rp::pac::IO_BANK0 + .gpio(XIP_CS_PIN) + .ctrl() + .write(|w| w.set_funcsel(GPIO_FUNC_XIP_CS1)); + embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { + w.set_iso(false); + }); + + critical_section::with(|_cs| { + // Try and read the PSRAM ID via direct_csr. + qmi.direct_csr().write(|w| { + w.set_clkdiv(30); + w.set_en(true); + }); + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + // Exit out of QMI in case we've inited already + qmi.direct_csr().modify(|w| w.set_assert_cs1n(true)); + + // Transmit the command to exit QPI quad mode - read ID as standard SPI + // Transmit as quad. + qmi.direct_tx().write(|w| { + w.set_oe(true); + w.set_iwidth(embassy_rp::pac::qmi::vals::Iwidth::Q); + w.set_data(PSRAM_CMD_QUAD_END.into()); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let _ = qmi.direct_rx().read(); + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + }); + + // Read the id + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(true); + }); + + // kgd is "known good die" + let mut kgd: u16 = 0; + let mut eid: u16 = 0; + for i in 0usize..7 { + qmi.direct_tx().write(|w| { + w.set_data(if i == 0 { + PSRAM_CMD_READ_ID.into() + } else { + PSRAM_CMD_NOOP.into() + }) + }); + + while !qmi.direct_csr().read().txempty() { + // rp235x_hal::arch::nop(); + } + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + let value = qmi.direct_rx().read().direct_rx(); + match i { + 5 => { + kgd = value; + } + 6 => { + eid = value; + } + _ => {} + } + } + + qmi.direct_csr().modify(|w| { + w.set_assert_cs1n(false); + w.set_en(false); + }); + let mut param_size: u32 = 0; + if kgd == PSRAM_KNOWN_GOOD_DIE_PASS as u16 { + param_size = 1024 * 1024; + let size_id = eid >> 5; + if eid == 0x26 || size_id == 2 { + param_size *= 8; + } else if size_id == 0 { + param_size *= 2; + } else if size_id == 1 { + param_size *= 4; + } + } + param_size + }) +} + +#[unsafe(link_section = ".data")] +#[inline(never)] +pub fn init_psram_qmi( + qmi: &embassy_rp::pac::qmi::Qmi, + xip: &embassy_rp::pac::xip_ctrl::XipCtrl, +) -> u32 { + let psram_size = detect_psram_qmi(qmi); + + if psram_size == 0 { + #[cfg(feature = "debug")] + defmt::error!("qmi psram size 0"); + return 0; + } + + let clock_hz = clk_peri_freq(); + + let mut divisor: u32 = (clock_hz + MAX_PSRAM_FREQ - 1) / MAX_PSRAM_FREQ; + if divisor == 1 && clock_hz > 100_000_000 { + divisor = 2; + } + let mut rxdelay: u32 = divisor; + if clock_hz / divisor > 100_000_000 { + rxdelay += 1; + } + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); + let max_select: u8 = ((125 * 1_000_000) / clock_period_fs) as u8; + let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs + - u64::from(divisor + 1) / 2) as u32; + + // #[cfg(feature = "defmt")] + // defmt::info!( + // "clock_period_fs={} max_select={} min_deselect={}", + // clock_period_fs, + // max_select, + // min_deselect + // ); + + qmi.direct_csr().write(|w| { + w.set_clkdiv(10); + w.set_en(true); + w.set_auto_cs1n(true); + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.direct_tx().write(|w| { + w.set_nopush(true); + w.0 = 0x35; + }); + + while qmi.direct_csr().read().busy() { + // rp235x_hal::arch::nop(); + } + + qmi.mem(1).timing().write(|w| { + w.set_cooldown(1); + w.set_pagebreak(embassy_rp::pac::qmi::vals::Pagebreak::_1024); + w.set_max_select(max_select as u8); + w.set_min_deselect(min_deselect as u8); + w.set_rxdelay(rxdelay as u8); + w.set_clkdiv(divisor as u8); + }); + + // // Set PSRAM commands and formats + qmi.mem(1).rfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + w.set_dummy_len(embassy_rp::pac::qmi::vals::DummyLen::_24); + }); + + qmi.mem(1).rcmd().write(|w| w.0 = 0xEB); + + qmi.mem(1).wfmt().write(|w| { + w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); + w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); + w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); + w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); + w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); + w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); + }); + + qmi.mem(1).wcmd().write(|w| w.0 = 0x38); + + // Disable direct mode + qmi.direct_csr().write(|w| w.0 = 0); + + // Enable writes to PSRAM + xip.ctrl().modify(|w| w.set_writable_m1(true)); + psram_size +} diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs index b61acee..9618563 100644 --- a/kernel/src/scsi/mod.rs +++ b/kernel/src/scsi/mod.rs @@ -54,6 +54,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await; if MSC_SHUTDOWN.signaled() { + #[cfg(feature = "defmt")] defmt::info!("MSC shutting down"); if self.temp_sd.is_some() { @@ -80,6 +81,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { if let Some(sd) = guard.take() { self.temp_sd = Some(sd); } else { + #[cfg(feature = "defmt")] defmt::warn!("Tried to take SDCARD but it was already taken"); return; } @@ -363,6 +365,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { } pub async fn send_csw_fail(&mut self, tag: u32) { + #[cfg(feature = "defmt")] defmt::error!("Command Failed: {}", tag); self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed } diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 15093d8..0632fa5 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -35,12 +35,24 @@ impl TimeSource for DummyTimeSource { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct FileName { pub long_name: String, pub short_name: ShortFileName, } +impl PartialOrd for FileName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.long_name.cmp(&other.long_name)) + } +} + +impl Ord for FileName { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.long_name.cmp(&other.long_name) + } +} + pub struct SdCard { det: Input<'static>, volume_mgr: VolMgr, diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index f4b9d85..2a304a9 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -1,10 +1,8 @@ use crate::{ - BINARY_CH, - display::{FB_PAUSED, FRAMEBUFFER}, - elf::load_binary, - peripherals::keyboard, - storage::FileName, + BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED, + peripherals::keyboard, storage::FileName, }; +use abi_sys::keyboard::{KeyCode, KeyState}; use alloc::{str::FromStr, string::String, vec::Vec}; use core::sync::atomic::Ordering; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; @@ -22,7 +20,6 @@ use embedded_layout::{ prelude::*, }; use embedded_text::TextBox; -use shared::keyboard::{KeyCode, KeyState}; pub static SELECTIONS: Mutex = Mutex::new(SelectionList::new()); @@ -71,17 +68,17 @@ pub async fn clear_selection() { if let Some(area) = sel.last_bounds { Rectangle::new(area.top_left, area.size) .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); } } async fn draw_selection() { let mut guard = SELECTIONS.lock().await; - let file_names = &guard.selections.clone(); + let file_names = guard.selections.clone(); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); - let display_area = unsafe { FRAMEBUFFER.bounding_box() }; + let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() }; const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; let no_bins = String::from_str(NO_BINS).unwrap(); @@ -97,12 +94,12 @@ async fn draw_selection() { ), text_style, ) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); } else { let mut views: alloc::vec::Vec>> = Vec::new(); - for i in file_names { + for i in &file_names { views.push(Text::new(&i.long_name, Point::zero(), text_style)); } @@ -122,12 +119,14 @@ async fn draw_selection() { .bounding_box(); Rectangle::new(selected_bounds.top_left, selected_bounds.size) .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) - .draw(unsafe { &mut FRAMEBUFFER }) + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) .unwrap(); guard.last_bounds = Some(layout.bounds()); - layout.draw(unsafe { &mut FRAMEBUFFER }).unwrap(); + layout + .draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() }) + .unwrap(); } guard.changed = false; diff --git a/picolibc b/picolibc new file mode 160000 index 0000000..d664643 --- /dev/null +++ b/picolibc @@ -0,0 +1 @@ +Subproject commit d664643068a0a300858af29b2738bd7d38dd7ab5 diff --git a/selection_ui/Cargo.toml b/selection_ui/Cargo.toml new file mode 100644 index 0000000..263d6e3 --- /dev/null +++ b/selection_ui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "selection_ui" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../abi" } +embedded-graphics = "0.8.1" +embedded-layout = "0.4.2" +embedded-text = "0.7.3" diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs new file mode 100644 index 0000000..ac4e9ab --- /dev/null +++ b/selection_ui/src/lib.rs @@ -0,0 +1,148 @@ +#![no_std] + +extern crate alloc; + +use abi::{ + display::Display, + get_key, + keyboard::{KeyCode, KeyState}, +}; +use alloc::vec::Vec; +use embedded_graphics::{ + Drawable, + mono_font::{MonoTextStyle, ascii::FONT_10X20}, + pixelcolor::Rgb565, + prelude::{Dimensions, DrawTarget, Point, Primitive, RgbColor}, + primitives::{PrimitiveStyle, Rectangle}, + text::{Alignment, Text, renderer::TextRenderer}, +}; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::{FixedMargin, LinearLayout}, + prelude::*, +}; + +#[derive(Debug)] +pub enum SelectionUiError { + SelectionListEmpty, + DisplayError(DisplayError), +} + +pub struct SelectionUi<'a> { + selection: usize, + items: &'a [&'a str], + last_bounds: Option, +} + +impl<'a> SelectionUi<'a> { + pub fn new(items: &'a [&'a str]) -> Self { + Self { + selection: 0, + items, + last_bounds: None, + } + } + + pub fn run_selection_ui( + &mut self, + display: &mut Display, + ) -> Result, SelectionUiError<::Error>> { + self.draw(display)?; + let selection; + loop { + let key = get_key(); + if key.state == KeyState::Pressed { + if let Some(s) = self.update(display, key.key)? { + selection = Some(s); + display + .clear(Rgb565::BLACK) + .map_err(|e| SelectionUiError::DisplayError(e))?; + break; + } + } + } + Ok(selection) + } + + /// updates the display with a new keypress. + /// returns selection idx if selected + pub fn update( + &mut self, + display: &mut Display, + key: KeyCode, + ) -> Result, SelectionUiError<::Error>> { + match key { + KeyCode::Down => { + self.selection = (self.selection + 1).min(self.items.len() - 1); + } + KeyCode::Up => { + self.selection = self.selection.saturating_sub(1); + } + KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)), + _ => return Ok(None), + }; + self.draw(display)?; + Ok(None) + } + + fn draw( + &mut self, + display: &mut Display, + ) -> Result<(), SelectionUiError<::Error>> { + let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); + let display_area = display.bounding_box(); + + if self.items.is_empty() { + return Err(SelectionUiError::SelectionListEmpty); + } + + if let Some(bounds) = self.last_bounds { + Rectangle::new(bounds.top_left, bounds.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; + } + + let mut views: Vec>> = Vec::new(); + + for i in self.items { + views.push(Text::new(i, Point::zero(), text_style)); + } + + let views_group = Views::new(views.as_mut_slice()); + + let layout = LinearLayout::vertical(views_group) + .with_alignment(horizontal::Center) + .with_spacing(FixedMargin(5)) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center); + + layout + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; + + // draw selected box + if let Some(selected_bounds) = layout.inner().get(self.selection) { + let selected_bounds = selected_bounds.bounding_box(); + Rectangle::new(selected_bounds.top_left, selected_bounds.size) + .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) + .draw(display) + .map_err(|e| SelectionUiError::DisplayError(e))?; + + self.last_bounds = Some(selected_bounds); + } + + Ok(()) + } +} + +pub fn draw_text_center<'a, S>( + display: &mut Display, + text: &'a str, + style: S, +) -> Result::Error> +where + S: TextRenderer::Color>, +{ + Text::with_alignment(text, Point::zero(), style, Alignment::Center).draw(display) +} diff --git a/shared/Cargo.toml b/shared/Cargo.toml deleted file mode 100644 index 89519f1..0000000 --- a/shared/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "shared" -version = "0.1.0" -edition = "2024" - -[features] -default = [] -defmt = ["dep:defmt"] - -[dependencies] -bitflags = "2.9.4" -defmt = { version = "1.0.1", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs deleted file mode 100644 index b78e205..0000000 --- a/shared/src/lib.rs +++ /dev/null @@ -1,144 +0,0 @@ -#![no_std] - -pub mod keyboard { - bitflags::bitflags! { - #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] - pub struct Modifiers: u8 { - const NONE = 0; - const CTRL = 1; - const ALT = 2; - const LSHIFT = 4; - const RSHIFT = 8; - const SYM = 16; - } - } - - #[derive(Debug)] - #[repr(C)] - pub struct KeyEvent { - pub key: KeyCode, - pub state: KeyState, - pub mods: Modifiers, - } - - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(u8)] - pub enum KeyState { - Idle = 0, - Pressed = 1, - Hold = 2, - Released = 3, - } - - impl From for KeyState { - fn from(value: u8) -> Self { - match value { - 1 => KeyState::Pressed, - 2 => KeyState::Hold, - 3 => KeyState::Released, - 0 | _ => KeyState::Idle, - } - } - } - - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(C)] - #[repr(u8)] - pub enum KeyCode { - JoyUp = 0x01, - JoyDown = 0x02, - JoyLeft = 0x03, - JoyRight = 0x04, - JoyCenter = 0x05, - BtnLeft1 = 0x06, - BtnRight1 = 0x07, - BtnLeft2 = 0x11, - BtnRight2 = 0x12, - Backspace = 0x08, - Tab = 0x09, - Enter = 0x0A, - ModAlt = 0xA1, - ModShiftLeft = 0xA2, - ModShiftRight = 0xA3, - ModSym = 0xA4, - ModCtrl = 0xA5, - Esc = 0xB1, - Left = 0xB4, - Up = 0xB5, - Down = 0xB6, - Right = 0xB7, - Break = 0xD0, - Insert = 0xD1, - Home = 0xD2, - Del = 0xD4, - End = 0xD5, - PageUp = 0xD6, - PageDown = 0xD7, - CapsLock = 0xC1, - F1 = 0x81, - F2 = 0x82, - F3 = 0x83, - F4 = 0x84, - F5 = 0x85, - F6 = 0x86, - F7 = 0x87, - F8 = 0x88, - F9 = 0x89, - F10 = 0x90, - Char(char), - Unknown(u8), - } - - impl From for KeyCode { - fn from(value: u8) -> Self { - match value { - 0x01 => Self::JoyUp, - 0x02 => Self::JoyDown, - 0x03 => Self::JoyLeft, - 0x04 => Self::JoyRight, - 0x05 => Self::JoyCenter, - 0x06 => Self::BtnLeft1, - 0x07 => Self::BtnRight1, - 0x08 => Self::Backspace, - 0x09 => Self::Tab, - 0x0A => Self::Enter, - 0x11 => Self::BtnLeft2, - 0x12 => Self::BtnRight2, - 0xA1 => Self::ModAlt, - 0xA2 => Self::ModShiftLeft, - 0xA3 => Self::ModShiftRight, - 0xA4 => Self::ModSym, - 0xA5 => Self::ModCtrl, - 0xB1 => Self::Esc, - 0xB4 => Self::Left, - 0xB5 => Self::Up, - 0xB6 => Self::Down, - 0xB7 => Self::Right, - 0xC1 => Self::CapsLock, - 0xD0 => Self::Break, - 0xD1 => Self::Insert, - 0xD2 => Self::Home, - 0xD4 => Self::Del, - 0xD5 => Self::End, - 0xD6 => Self::PageUp, - 0xD7 => Self::PageDown, - 0x81 => Self::F1, - 0x82 => Self::F2, - 0x83 => Self::F3, - 0x84 => Self::F4, - 0x85 => Self::F5, - 0x86 => Self::F6, - 0x87 => Self::F7, - 0x88 => Self::F8, - 0x89 => Self::F9, - 0x90 => Self::F10, - _ => match char::from_u32(value as u32) { - Some(c) => Self::Char(c), - None => Self::Unknown(value), - }, - } - } - } -} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 54b9cbb..0b42eb9 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -2,7 +2,12 @@ #![no_main] extern crate alloc; -use abi::{KeyCode, KeyState, display::Display, get_key, lock_display, print}; +use abi::{ + display::Display, + get_key, + keyboard::{KeyCode, KeyState}, + println, +}; use alloc::{format, string::String, vec, vec::Vec}; use core::panic::PanicInfo; use embedded_graphics::{ @@ -23,11 +28,7 @@ use embedded_layout::{ #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -37,8 +38,8 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Calculator app"); - let mut display = Display; + println!("Starting Calculator app"); + let mut display = Display::take().unwrap(); let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; let input_min = input.len(); @@ -57,8 +58,6 @@ pub fn main() { loop { if dirty { - lock_display(true); - let style = PrimitiveStyle::with_fill(Rgb565::BLACK); if let Some(area) = last_area { Rectangle::new(area.0.top_left, area.0.size) @@ -102,11 +101,10 @@ pub fn main() { eq_layout.draw(&mut display).unwrap(); dirty = false; - lock_display(false); } let event = get_key(); - if event.state != KeyState::Idle { + if event.state == KeyState::Released { match event.key { KeyCode::Char(ch) => { input.push(ch); diff --git a/user-apps/gallery/src/main.rs b/user-apps/gallery/src/main.rs index a896070..17c5dfc 100644 --- a/user-apps/gallery/src/main.rs +++ b/user-apps/gallery/src/main.rs @@ -4,11 +4,13 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, list_dir, lock_display, print, read_file, + fs::{Entries, list_dir, read_file}, + get_key, + keyboard::{KeyCode, KeyState}, + println, }; -use alloc::{format, string::ToString}; +use alloc::{format, vec}; use core::panic::PanicInfo; use embedded_graphics::{ Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10, @@ -18,11 +20,7 @@ use tinybmp::Bmp; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + println!("user panic: {} @ {:?}", info.message(), info.location()); loop {} } @@ -32,11 +30,10 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Gallery app"); - static mut BMP_BUF: [u8; 100_000] = [0_u8; 100_000]; - let mut display = Display; + println!("Starting Gallery app"); + let mut bmp_buf = vec![0_u8; 100_000]; + let mut display = Display::take().unwrap(); - // Grid parameters let grid_cols = 3; let grid_rows = 3; let cell_width = SCREEN_WIDTH as i32 / grid_cols; @@ -44,53 +41,44 @@ pub fn main() { let mut images_drawn = 0; - let mut files = [const { None }; 18]; - let files_num = list_dir("/images", &mut files); + let mut entries = Entries::new(); + let files_num = list_dir("/images", &mut entries); - for file in &files[2..files_num] { + for file in &entries.entries()[2..files_num] { if images_drawn >= grid_cols * grid_rows { break; // only draw 3x3 } - if let Some(f) = file { - print(&format!("file: {}", f.name)); - if f.name.extension() == b"bmp" || f.name.extension() == b"BMP" { - let file = format!("/images/{}", f.name); + println!("file: {}", file); + if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" { + let file_path = format!("/images/{}", file); - let read = read_file(&file, 0, &mut unsafe { &mut BMP_BUF[..] }); - if read > 0 { - let bmp = Bmp::from_slice(unsafe { &BMP_BUF }).expect("failed to parse bmp"); + let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]); + if read > 0 { + let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); - let row = images_drawn / grid_cols; - let col = images_drawn % grid_cols; - let cell_x = col * cell_width; - let cell_y = row * cell_height; + let row = images_drawn / grid_cols; + let col = images_drawn % grid_cols; + let cell_x = col * cell_width; + let cell_y = row * cell_height; - // Center image inside cell - let bmp_w = bmp.size().width as i32; - let bmp_h = bmp.size().height as i32; - let x = cell_x + (cell_width - bmp_w) / 2; - let y = cell_y + 5; // 5px top margin + // Center image inside cell + let bmp_w = bmp.size().width as i32; + let bmp_h = bmp.size().height as i32; + let x = cell_x + (cell_width - bmp_w) / 2; + let y = cell_y + 5; // 5px top margin - lock_display(true); - Image::new(&bmp, Point::new(x, y)) - .draw(&mut display) - .unwrap(); - - let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); - let text_y = y + bmp_h + 2; // 2px gap under image - Text::new( - f.name.to_string().as_str(), - Point::new(cell_x + 2, text_y), - text_style, - ) + Image::new(&bmp, Point::new(x, y)) .draw(&mut display) .unwrap(); - lock_display(false); + let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + let text_y = y + bmp_h + 2; // 2px gap under image + Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style) + .draw(&mut display) + .unwrap(); - images_drawn += 1; - } + images_drawn += 1; } } } diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml new file mode 100644 index 0000000..cc45976 --- /dev/null +++ b/user-apps/gif/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gif" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../../abi" } +selection_ui = { path = "../../selection_ui" } +embedded-graphics = "0.8.1" +tinygif = { git = "https://github.com/LegitCamper/tinygif" } diff --git a/user-apps/gif/build.rs b/user-apps/gif/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/gif/build.rs @@ -0,0 +1,28 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("../memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); +} diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs new file mode 100644 index 0000000..e449096 --- /dev/null +++ b/user-apps/gif/src/main.rs @@ -0,0 +1,103 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use abi::{ + display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, + fs::{Entries, file_len, list_dir, read_file}, + get_key, get_ms, + keyboard::{KeyCode, KeyState}, + println, sleep, +}; +use alloc::{format, vec, vec::Vec}; +use core::panic::PanicInfo; +use embedded_graphics::{ + image::ImageDrawable, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::{Point, RgbColor}, + transform::Transform, +}; +use selection_ui::{SelectionUi, SelectionUiError, draw_text_center}; +use tinygif::Gif; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("user panic: {} @ {:?}", info.message(), info.location(),); + loop {} +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() { + main() +} + +pub fn main() { + println!("Starting Gif app"); + let mut display = Display::take().unwrap(); + + let mut entries = Entries::new(); + list_dir("/gifs", &mut entries); + + let mut files = entries.entries(); + files.retain(|e| e.extension().unwrap_or("") == "gif"); + let mut gifs = files.iter().map(|e| e.full_name()).collect::>(); + gifs.sort(); + + let mut selection_ui = SelectionUi::new(&mut gifs); + let selection = match selection_ui.run_selection_ui(&mut display) { + Ok(maybe_sel) => maybe_sel, + Err(e) => match e { + SelectionUiError::SelectionListEmpty => { + draw_text_center( + &mut display, + "No Gifs were found in /gifs", + MonoTextStyle::new(&FONT_6X10, Rgb565::RED), + ) + .expect("Display Error"); + None + } + SelectionUiError::DisplayError(_) => panic!("Display Error"), + }, + }; + + assert!(selection.is_some()); + + let file_name = format!("/gifs/{}", gifs[selection.unwrap()]); + let size = file_len(&file_name); + let mut buf = vec![0_u8; size]; + let read = read_file(&file_name, 0, &mut buf); + println!("read: {}, file size: {}", read, size); + assert!(read == size); + + let gif = Gif::::from_slice(&buf).expect("Failed to parse gif"); + + let translation = Point::new( + (SCREEN_WIDTH as i32 - gif.width() as i32) / 2, + (SCREEN_HEIGHT as i32 - gif.height() as i32) / 2, + ); + + let mut frame_num = 0; + loop { + for mut frame in gif.frames() { + let start = get_ms(); + + frame.translate_mut(translation).draw(&mut display).unwrap(); + frame_num += 1; + + if frame_num % 5 == 0 { + let event = get_key(); + if event.state != KeyState::Idle { + match event.key { + KeyCode::Esc => { + drop(buf); + return; + } + _ => (), + }; + }; + } + sleep(((frame.delay_centis as u64) * 10).saturating_sub(start)); + } + } +} diff --git a/user-apps/memory.x b/user-apps/memory.x index 5517509..71677ed 100644 --- a/user-apps/memory.x +++ b/user-apps/memory.x @@ -1,6 +1,6 @@ MEMORY { - RAM : ORIGIN = 0x0, LENGTH = 150K + RAM : ORIGIN = 0x0, LENGTH = 250K } SECTIONS diff --git a/user-apps/snake/Cargo.toml b/user-apps/snake/Cargo.toml index 5ce4e74..7eb0d67 100644 --- a/user-apps/snake/Cargo.toml +++ b/user-apps/snake/Cargo.toml @@ -6,5 +6,5 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } embedded-graphics = "0.8.1" -embedded-snake = { path = "../../../embedded-snake-rs" } +embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" } rand = { version = "0.9.0", default-features = false } diff --git a/user-apps/snake/src/main.rs b/user-apps/snake/src/main.rs index 78d9451..a1e078a 100644 --- a/user-apps/snake/src/main.rs +++ b/user-apps/snake/src/main.rs @@ -3,22 +3,19 @@ extern crate alloc; use abi::{ - KeyCode, KeyState, Rng, + Rng, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - get_key, lock_display, print, sleep, + get_key, + keyboard::{KeyCode, KeyState}, + println, sleep, }; -use alloc::format; use core::panic::PanicInfo; use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor}; use embedded_snake::{Direction, SnakeGame}; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -30,8 +27,8 @@ pub extern "Rust" fn _start() { const CELL_SIZE: usize = 8; pub fn main() { - print("Starting Snake app"); - let mut display = Display; + println!("Starting Snake app"); + let mut display = Display::take().unwrap(); let mut game = SnakeGame::<100, Rgb565, Rng>::new( SCREEN_WIDTH as u16, @@ -60,10 +57,8 @@ pub fn main() { }; // ensure all draws show up at once - lock_display(true); game.pre_draw(&mut display); game.draw(&mut display); - lock_display(false); sleep(15); } diff --git a/user-apps/wav_player/src/main.rs b/user-apps/wav_player/src/main.rs index 3dafe70..46d3277 100644 --- a/user-apps/wav_player/src/main.rs +++ b/user-apps/wav_player/src/main.rs @@ -3,22 +3,20 @@ extern crate alloc; use abi::{ - AUDIO_BUFFER_LEN, KeyCode, KeyState, Rng, audio_buffer_ready, - display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, - file_len, get_key, lock_display, print, read_file, send_audio_buffer, sleep, + audio::{AUDIO_BUFFER_LEN, audio_buffer_ready, send_audio_buffer}, + display::Display, + fs::{file_len, read_file}, + get_key, + keyboard::{KeyCode, KeyState}, + println, }; -use alloc::{format, string::String}; +use alloc::string::String; use core::panic::PanicInfo; use embedded_audio::{AudioFile, PlatformFile, PlatformFileError, wav::Wav}; -use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor}; #[panic_handler] fn panic(info: &PanicInfo) -> ! { - print(&format!( - "user panic: {} @ {:?}", - info.message(), - info.location(), - )); + println!("user panic: {} @ {:?}", info.message(), info.location(),); loop {} } @@ -28,8 +26,8 @@ pub extern "Rust" fn _start() { } pub fn main() { - print("Starting Wav player app"); - let mut display = Display; + println!("Starting Wav player app"); + let mut _display = Display::take(); let mut buf = [0_u8; AUDIO_BUFFER_LEN]; @@ -41,7 +39,7 @@ pub fn main() { wav.restart().unwrap() } - let read = wav.read(&mut buf).unwrap(); + let _read = wav.read(&mut buf).unwrap(); send_audio_buffer(&buf); }