feat: implement "Pro-Controller" mode (#26)
Based on the pro-controller code from https://github.com/HandHeldLegend/HOJA-LIB-RP2040 Author has given their consent for this to be GPL'd: ![image](/attachments/c2551d3a-9b35-4c67-ad28-3778f9e04e29) Reviewed-on: #26
This commit is contained in:
parent
4e1af50ebd
commit
8f8d47704d
15 changed files with 1505 additions and 631 deletions
12
.changelogs/v1.2.0.md
Normal file
12
.changelogs/v1.2.0.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
This release introduces a major new feature to combat a regression introduced in Switch firmware 19.0.0. For those unaware, Switch firmware 19.0.0 breaks compatibility with GCC adapters, including NaxGCC, which advertises itself as a GCC adapter.
|
||||
|
||||
Starting with this version, you will be able to connect NaxGCC in "Pro-Controller Mode" by pressing and holding `Start` while plugging it in. This will cause the NaxGCC to advertise itself as a Nintendo Switch Pro Controller, and therefore be unaffected by the bug in the latest Switch firmware. All input consistency modes remain fully functional while in this mode, and your settings (including calibration) carry over as well.
|
||||
|
||||
While in Pro-Controller Mode, pressing `Z+Start` will act like the home button on a regular Pro Controller. Additionally, pressing `L` will press both `L` and `ZL` at the same time (since the GCC only has one left shoulder button). This is useful if you want to map things like jump (short-hop macro) or shield (prevent rolling) to it.
|
||||
|
||||
> [!NOTE]
|
||||
> As of this version, rumble will _not_ work while in Pro Controller Mode.
|
||||
|
||||
---
|
||||
|
||||
To update your firmware, plug in your controller to your computer while keeping the `A+X+Y` buttons held. Then drag & drop the `.uf2` file (found below, under Downloads) onto the storage device that appears.
|
|
@ -10,8 +10,19 @@ jobs:
|
|||
check:
|
||||
runs-on: nix-flakes
|
||||
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up attic binary cache
|
||||
uses: https://git.naxdy.org/NaxdyOrg/attic-action@v0.3
|
||||
with:
|
||||
endpoint: "${{ vars.BINARY_CACHE_URL }}"
|
||||
token: ""
|
||||
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
|
||||
skip-push: true
|
||||
|
||||
- name: Run Clippy
|
||||
run: |
|
||||
nix develop . --command cargo clippy -- -Dwarnings
|
||||
nix build .#clippy --print-build-logs -j auto
|
||||
|
|
|
@ -20,9 +20,15 @@ jobs:
|
|||
token: "${{ secrets.PUBLIC_BINARY_CACHE_AUTH_KEY }}"
|
||||
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Clippy
|
||||
run: |
|
||||
nix build .#clippy --print-build-logs -j auto
|
||||
|
||||
- name: Build firmware image
|
||||
run: |
|
||||
nix build .# -o dist --print-build-logs
|
||||
|
||||
- name: (Re-)generate tag
|
||||
run: |
|
||||
git config --global user.email "noreply@naxdy.org"
|
||||
|
@ -32,6 +38,7 @@ jobs:
|
|||
git tag nightly -m "Nightly Release"
|
||||
git checkout nightly
|
||||
git push --set-upstream origin nightly --force
|
||||
|
||||
- name: Publish nightly release
|
||||
uses: https://gitea.com/actions/gitea-release-action@v1.3.0
|
||||
with:
|
||||
|
|
163
Cargo.lock
generated
163
Cargo.lock
generated
|
@ -2,6 +2,18 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
|
@ -77,6 +89,12 @@ version = "0.13.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||
|
||||
[[package]]
|
||||
name = "bitfield"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -109,9 +127,9 @@ checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
|
|||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -136,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
|
||||
dependencies = [
|
||||
"bare-metal",
|
||||
"bitfield",
|
||||
"bitfield 0.13.2",
|
||||
"embedded-hal 0.2.7",
|
||||
"volatile-register",
|
||||
]
|
||||
|
@ -309,7 +327,8 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
|||
|
||||
[[package]]
|
||||
name = "embassy-embedded-hal"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"embassy-futures",
|
||||
|
@ -325,7 +344,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "embassy-executor"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"critical-section",
|
||||
|
@ -338,7 +358,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "embassy-executor-macros"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
|
@ -349,13 +370,15 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "embassy-futures"
|
||||
version = "0.1.1"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-hal-internal"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"critical-section",
|
||||
|
@ -366,10 +389,12 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "embassy-net-driver"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
|
||||
[[package]]
|
||||
name = "embassy-net-driver-channel"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"embassy-futures",
|
||||
"embassy-net-driver",
|
||||
|
@ -378,7 +403,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "embassy-rp"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"atomic-polyfill",
|
||||
"cfg-if",
|
||||
|
@ -403,7 +429,6 @@ dependencies = [
|
|||
"embedded-storage",
|
||||
"embedded-storage-async",
|
||||
"fixed",
|
||||
"futures",
|
||||
"nb 1.1.0",
|
||||
"pio",
|
||||
"pio-proc",
|
||||
|
@ -414,7 +439,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "embassy-sync"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"critical-section",
|
||||
|
@ -426,7 +452,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "embassy-time"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"critical-section",
|
||||
|
@ -444,6 +471,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "embassy-time-driver"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"document-features",
|
||||
]
|
||||
|
@ -451,10 +479,12 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "embassy-time-queue-driver"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
|
||||
[[package]]
|
||||
name = "embassy-usb"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"embassy-futures",
|
||||
|
@ -469,6 +499,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "embassy-usb-driver"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
@ -630,59 +661,12 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.30"
|
||||
|
@ -696,8 +680,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
|
@ -733,6 +715,15 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
|
@ -768,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -875,7 +866,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
|||
|
||||
[[package]]
|
||||
name = "naxgcc-fw"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
|
@ -1453,15 +1444,19 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
|||
|
||||
[[package]]
|
||||
name = "usb-device"
|
||||
version = "0.2.9"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508"
|
||||
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usbd-hid"
|
||||
version = "0.6.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132"
|
||||
checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"ssmarshal",
|
||||
|
@ -1471,20 +1466,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "usbd-hid-descriptors"
|
||||
version = "0.1.2"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1"
|
||||
checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed"
|
||||
dependencies = [
|
||||
"bitfield",
|
||||
"bitfield 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usbd-hid-macros"
|
||||
version = "0.6.0"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2"
|
||||
checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"hashbrown 0.13.2",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
|
@ -1687,3 +1684,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
|||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
|
41
Cargo.toml
41
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "naxgcc-fw"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -9,33 +9,33 @@ edition = "2021"
|
|||
embassy-time = { version = "0.3.0", features = [
|
||||
"defmt",
|
||||
"defmt-timestamp-uptime",
|
||||
], path = "lib/embassy-rs/embassy-time" }
|
||||
embassy-embedded-hal = { version = "0.1.0", features = [
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-embedded-hal = { version = "0.2.0", features = [
|
||||
"defmt",
|
||||
], path = "lib/embassy-rs/embassy-embedded-hal" }
|
||||
embassy-sync = { version = "0.5.0", features = [
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-sync = { version = "0.6.0", features = [
|
||||
"defmt",
|
||||
], path = "lib/embassy-rs/embassy-sync" }
|
||||
embassy-executor = { version = "0.5.0", features = [
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-executor = { version = "0.6.0", features = [
|
||||
"task-arena-size-32768",
|
||||
"arch-cortex-m",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
"defmt",
|
||||
"integrated-timers",
|
||||
], path = "lib/embassy-rs/embassy-executor" }
|
||||
embassy-rp = { version = "0.1.0", features = [
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-rp = { version = "0.2.0", features = [
|
||||
"defmt",
|
||||
"unstable-pac",
|
||||
"time-driver",
|
||||
"critical-section-impl",
|
||||
], path = "lib/embassy-rs/embassy-rp" }
|
||||
embassy-usb = { version = "0.1.0", features = [
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-usb = { version = "0.3.0", features = [
|
||||
"defmt",
|
||||
], path = "lib/embassy-rs/embassy-usb" }
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
embassy-futures = { version = "0.1.0", features = [
|
||||
"defmt",
|
||||
], path = "lib/embassy-rs/embassy-futures" }
|
||||
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.4"
|
||||
fixed = "1.23.1"
|
||||
|
@ -48,7 +48,7 @@ libm = { version = "0.2.8" }
|
|||
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
|
||||
cortex-m-rt = "0.7.0"
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
packed_struct = { version = "0.10.1", default_features = false }
|
||||
packed_struct = { version = "0.10.1", default-features = false }
|
||||
format_no_std = "1.0.2"
|
||||
rand = { version = "0.8.5", default-features = false }
|
||||
|
||||
|
@ -79,14 +79,14 @@ overflow-checks = false
|
|||
codegen-units = 8
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
opt-level = 0
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
[profile.release.build-override]
|
||||
codegen-units = 8
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
opt-level = 0
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
# cargo test
|
||||
|
@ -106,12 +106,3 @@ debug-assertions = false
|
|||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3
|
||||
|
||||
# [patch.crates-io]
|
||||
# embassy-rp = { path = "lib/embassy-rs/embassy-rp" }
|
||||
# embassy-time = { path = "lib/embassy-rs/embassy-time" }
|
||||
# embassy-embedded-hal = { path = "lib/embassy-rs/embassy-embedded-hal" }
|
||||
# embassy-usb = { path = "lib/embassy-rs/embassy-usb" }
|
||||
# embassy-sync = { path = "lib/embassy-rs/embassy-sync" }
|
||||
# embassy-executor = { path = "lib/embassy-rs/embassy-executor" }
|
||||
# embassy-futures = { path = "lib/embassy-rs/embassy-futures" }
|
||||
|
|
72
flake.lock
72
flake.lock
|
@ -1,22 +1,5 @@
|
|||
{
|
||||
"nodes": {
|
||||
"embassy-rs-patched": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1710754402,
|
||||
"narHash": "sha256-HyTw5VQXlSqz9UOq0Dc6G/NWNzQOPXf3PNWffMkfLC4=",
|
||||
"ref": "naxgcc-fw",
|
||||
"rev": "2ee4657727b9679998d941de00b43e1754f570bf",
|
||||
"revCount": 6754,
|
||||
"type": "git",
|
||||
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
},
|
||||
"original": {
|
||||
"ref": "naxgcc-fw",
|
||||
"type": "git",
|
||||
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
|
@ -35,24 +18,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
@ -75,27 +40,27 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1710283656,
|
||||
"narHash": "sha256-nI+AOy4uK6jLGBi9nsbHjL1EdSIzoo8oa+9oeVhbyFc=",
|
||||
"lastModified": 1728740863,
|
||||
"narHash": "sha256-u+rxA79a0lyhG+u+oPBRtTDtzz8kvkc9a6SWSt9ekVc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "51063ed4f2343a59fdeebb279bb81d87d453942b",
|
||||
"rev": "a3f9ad65a0bf298ed5847629a57808b97e6e8077",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-23.11",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1706487304,
|
||||
"narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
|
||||
"lastModified": 1718428119,
|
||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
|
||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -107,7 +72,6 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"embassy-rs-patched": "embassy-rs-patched",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
|
@ -116,15 +80,14 @@
|
|||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710382258,
|
||||
"narHash": "sha256-2FW1q+o34VBweYQiEkRaSEkNMq3ecrn83VzETeGiVbY=",
|
||||
"lastModified": 1728700003,
|
||||
"narHash": "sha256-Ox1pvEHxLK6lAdaKQW21Zvk65SPDag+cD8YA444R/og=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "8ce81e71ab04a7e906fae62da086d6ee5d6cfc21",
|
||||
"rev": "fc1e58ebabe0cef4442eedea07556ff0c9eafcfe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -147,21 +110,6 @@
|
|||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
42
flake.nix
42
flake.nix
|
@ -2,17 +2,12 @@
|
|||
description = "Firmware for the NaxGCC";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-23.11";
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-24.05";
|
||||
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
embassy-rs-patched = {
|
||||
url = "git+https://gitea@git.naxdy.org/NaxdyOrg/embassy?ref=naxgcc-fw";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
naersk = {
|
||||
url = "github:nmattia/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
@ -25,7 +20,6 @@
|
|||
, rust-overlay
|
||||
, flake-utils
|
||||
, naersk
|
||||
, embassy-rs-patched
|
||||
}: (flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
|
@ -51,12 +45,6 @@
|
|||
};
|
||||
|
||||
CARGO_BUILD_TARGET = "thumbv6m-none-eabi";
|
||||
|
||||
prepCmd = ''
|
||||
mkdir -p lib
|
||||
rm lib/embassy-rs || true
|
||||
ln -s "${embassy-rs-patched}" lib/embassy-rs
|
||||
'';
|
||||
in
|
||||
{
|
||||
packages.default = self.packages.${system}.naxgcc-fw-uf2;
|
||||
|
@ -66,20 +54,26 @@
|
|||
${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2
|
||||
'';
|
||||
|
||||
packages.naxgcc-fw = naersk_lib.buildPackage {
|
||||
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
|
||||
packages.naxgcc-fw = pkgs.callPackage
|
||||
({ mode ? "build" }: naersk_lib.buildPackage {
|
||||
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
|
||||
|
||||
prePatch = prepCmd;
|
||||
inherit mode;
|
||||
|
||||
src = self;
|
||||
src = self;
|
||||
|
||||
cargoBuildOptions = _orig: _orig ++ [
|
||||
"--target=${CARGO_BUILD_TARGET}"
|
||||
];
|
||||
cargoBuildOptions = _orig: _orig ++ [
|
||||
"--target=${CARGO_BUILD_TARGET}"
|
||||
];
|
||||
|
||||
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
|
||||
DEFMT_LOG = "off";
|
||||
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
|
||||
DEFMT_LOG = "off";
|
||||
})
|
||||
{ };
|
||||
|
||||
packages.clippy = self.packages.${system}.naxgcc-fw.override {
|
||||
mode = "clippy";
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
|
@ -92,8 +86,6 @@
|
|||
DEFMT_LOG = "debug";
|
||||
|
||||
inherit CARGO_BUILD_TARGET;
|
||||
|
||||
shellHook = prepCmd;
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
|
108
src/config.rs
108
src/config.rs
|
@ -16,8 +16,8 @@ use packed_struct::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
gcc_hid::{Buttons1, Buttons2, MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
|
||||
helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
|
||||
hid::gcc::{GcButtons1, GcButtons2, GcState},
|
||||
input::{
|
||||
read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
|
||||
},
|
||||
|
@ -26,6 +26,7 @@ use crate::{
|
|||
LinearizedCalibration, NotchCalibration, NotchStatus, CALIBRATION_ORDER,
|
||||
NOTCH_ADJUSTMENT_ORDER, NO_OF_ADJ_NOTCHES, NO_OF_CALIBRATION_POINTS, NO_OF_NOTCHES,
|
||||
},
|
||||
usb_comms::{MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
|
||||
ADDR_OFFSET, FLASH_SIZE,
|
||||
};
|
||||
|
||||
|
@ -36,7 +37,7 @@ use embassy_sync::{
|
|||
};
|
||||
use embassy_time::Timer;
|
||||
|
||||
use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE};
|
||||
use crate::input::CHANNEL_GCC_STATE;
|
||||
|
||||
/// Whether we are currently calibrating the sticks. Updates are dispatched when the status changes.
|
||||
/// Initial status is assumed to be false.
|
||||
|
@ -78,7 +79,7 @@ const MAX_ANALOG_SCALER: u8 = 125;
|
|||
/// a certain mode.
|
||||
#[derive(Default, Debug, Clone, Format)]
|
||||
pub struct OverrideGcReportInstruction {
|
||||
pub report: GcReport,
|
||||
pub report: GcState,
|
||||
pub duration_ms: u64,
|
||||
}
|
||||
|
||||
|
@ -563,6 +564,15 @@ pub enum InputConsistencyMode {
|
|||
PC = 3,
|
||||
}
|
||||
|
||||
/// Not saved, but set upon plugging in the controller.
|
||||
#[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
|
||||
pub enum ControllerMode {
|
||||
/// Advertise itself as a GCC adapter with 1 controller (itself) connected.
|
||||
GcAdapter = 0,
|
||||
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
|
||||
Procon = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Format, PackedStruct)]
|
||||
#[packed_struct(endian = "msb")]
|
||||
pub struct ControllerConfig {
|
||||
|
@ -637,6 +647,8 @@ impl ControllerConfig {
|
|||
/// Trait for providing button presses, used in the calibration process.
|
||||
trait ButtonPressProvider {
|
||||
/// Wait for a single button press.
|
||||
// TODO: remove allow once this is used somewhere
|
||||
#[allow(dead_code)]
|
||||
async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons);
|
||||
|
||||
/// Wait for a single button release.
|
||||
|
@ -670,7 +682,7 @@ trait ButtonPressProvider {
|
|||
}
|
||||
|
||||
impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPressProvider
|
||||
for Subscriber<'a, T, GcReport, I, J, K>
|
||||
for Subscriber<'a, T, GcState, I, J, K>
|
||||
{
|
||||
async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons) {
|
||||
loop {
|
||||
|
@ -770,7 +782,7 @@ impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPres
|
|||
}
|
||||
|
||||
pub fn is_awaitable_button_pressed(
|
||||
report: &GcReport,
|
||||
report: &GcState,
|
||||
button_to_wait_for: &AwaitableButtons,
|
||||
) -> bool {
|
||||
match button_to_wait_for {
|
||||
|
@ -1136,7 +1148,7 @@ async fn configuration_main_loop<
|
|||
>(
|
||||
current_config: &ControllerConfig,
|
||||
flash: &mut Flash<'static, FLASH, Async, FLASH_SIZE>,
|
||||
gcc_subscriber: &mut Subscriber<'a, M, GcReport, C, S, P>,
|
||||
gcc_subscriber: &mut Subscriber<'a, M, GcState, C, S, P>,
|
||||
) -> ControllerConfig {
|
||||
let mut final_config = current_config.clone();
|
||||
let config_options = [
|
||||
|
@ -1194,15 +1206,15 @@ async fn configuration_main_loop<
|
|||
// exit
|
||||
0 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1222,15 +1234,15 @@ async fn configuration_main_loop<
|
|||
// calibrate lstick
|
||||
1 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1251,15 +1263,15 @@ async fn configuration_main_loop<
|
|||
// calibrate rstick
|
||||
2 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1310,15 +1322,15 @@ async fn configuration_main_loop<
|
|||
.clamp(-ABS_MAX_SNAPBACK, ABS_MAX_SNAPBACK);
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1396,15 +1408,15 @@ async fn configuration_main_loop<
|
|||
.clamp(0, MAX_WAVESHAPING) as u8;
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1482,15 +1494,15 @@ async fn configuration_main_loop<
|
|||
.clamp(0, MAX_SMOOTHING) as u8;
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1557,15 +1569,15 @@ async fn configuration_main_loop<
|
|||
.clamp(-1, MAX_CARDINAL_SNAP);
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1613,15 +1625,15 @@ async fn configuration_main_loop<
|
|||
as u8;
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1661,16 +1673,16 @@ async fn configuration_main_loop<
|
|||
SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(*to_adjust);
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
button_z: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1697,15 +1709,15 @@ async fn configuration_main_loop<
|
|||
};
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1731,15 +1743,15 @@ async fn configuration_main_loop<
|
|||
// display waveshaping values on both sticks
|
||||
38 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1757,15 +1769,15 @@ async fn configuration_main_loop<
|
|||
// display stick smoothing values on both sticks
|
||||
39 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1783,15 +1795,15 @@ async fn configuration_main_loop<
|
|||
// display snapback values on both sticks
|
||||
40 => {
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1854,15 +1866,15 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
|
|||
info!("Entering config mode.");
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
report: GcState {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_l: true,
|
||||
button_r: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
|
198
src/hid/gcc.rs
Normal file
198
src/hid/gcc.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
use defmt::{info, trace, Format};
|
||||
use embassy_usb::{
|
||||
class::hid::{ReportId, RequestHandler},
|
||||
control::OutResponse,
|
||||
};
|
||||
|
||||
use crate::usb_comms::{HidReportBuilder, SIGNAL_RUMBLE};
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x05, // Usage (Game Pad)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x11, // Report ID (17)
|
||||
0x19, 0x00, // Usage Minimum (Undefined)
|
||||
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x21, // Report ID (33)
|
||||
0x05, 0x00, // Usage Page (Undefined)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0xFF, // Logical Maximum (-1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x08, // Usage Maximum (0x08)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x09, 0x32, // Usage (Z)
|
||||
0x09, 0x33, // Usage (Rx)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x15, 0x81, // Logical Minimum (-127)
|
||||
0x25, 0x7F, // Logical Maximum (127)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x13, // Report ID (19)
|
||||
0x19, 0x00, // Usage Minimum (Undefined)
|
||||
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct GcButtons1 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_a: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_b: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_x: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_y: bool,
|
||||
#[packed_field(bits = "4")]
|
||||
pub dpad_left: bool,
|
||||
#[packed_field(bits = "5")]
|
||||
pub dpad_right: bool,
|
||||
#[packed_field(bits = "6")]
|
||||
pub dpad_down: bool,
|
||||
#[packed_field(bits = "7")]
|
||||
pub dpad_up: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct GcButtons2 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_start: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_z: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_r: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_l: bool,
|
||||
#[packed_field(bits = "4..=7")]
|
||||
pub blank1: u8,
|
||||
}
|
||||
|
||||
///
|
||||
/// Struct representing the controller state. Used for HID communications in
|
||||
/// GCC adapter mode, as well as for the internal representation of the controller.
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
|
||||
pub struct GcState {
|
||||
#[packed_field(bits = "0..=7")]
|
||||
pub buttons_1: GcButtons1,
|
||||
#[packed_field(bits = "8..=15")]
|
||||
pub buttons_2: GcButtons2,
|
||||
#[packed_field(bits = "16..=23")]
|
||||
pub stick_x: u8,
|
||||
#[packed_field(bits = "24..=31")]
|
||||
pub stick_y: u8,
|
||||
#[packed_field(bits = "32..=39")]
|
||||
pub cstick_x: u8,
|
||||
#[packed_field(bits = "40..=47")]
|
||||
pub cstick_y: u8,
|
||||
#[packed_field(bits = "48..=55")]
|
||||
pub trigger_l: u8,
|
||||
#[packed_field(bits = "56..=63")]
|
||||
pub trigger_r: u8,
|
||||
}
|
||||
|
||||
impl Default for GcState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buttons_1: GcButtons1::default(),
|
||||
buttons_2: GcButtons2::default(),
|
||||
stick_x: 127,
|
||||
stick_y: 127,
|
||||
cstick_x: 127,
|
||||
cstick_y: 127,
|
||||
trigger_l: 0,
|
||||
trigger_r: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GcReportBuilder {
|
||||
gc_first: bool,
|
||||
}
|
||||
|
||||
impl HidReportBuilder<37> for GcReportBuilder {
|
||||
async fn get_hid_report(&mut self, state: &GcState) -> [u8; 37] {
|
||||
let mut buffer = [0u8; 37];
|
||||
|
||||
buffer[0] = 0x21;
|
||||
buffer[1] |= 0x14;
|
||||
|
||||
let data = state.pack().expect("Failed to pack GC input data");
|
||||
|
||||
if !self.gc_first {
|
||||
buffer[1] |= 0x04;
|
||||
buffer[10] |= 0x04;
|
||||
buffer[19] |= 0x04;
|
||||
buffer[28] |= 0x04;
|
||||
self.gc_first = true;
|
||||
} else {
|
||||
// controller in "port 1"
|
||||
buffer[2..=9].copy_from_slice(&data);
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GccRequestHandler;
|
||||
|
||||
impl RequestHandler for GccRequestHandler {
|
||||
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
|
||||
info!("Get report for {:?}", id);
|
||||
None
|
||||
}
|
||||
|
||||
fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
|
||||
trace!("Set report for {:?}: {:x}", id, data);
|
||||
|
||||
if data.len() > 1 {
|
||||
SIGNAL_RUMBLE.signal((data[1] & 0x01) != 0);
|
||||
}
|
||||
|
||||
OutResponse::Accepted
|
||||
}
|
||||
|
||||
fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
|
||||
info!("Set idle rate for {:?} to {:?}", id, dur);
|
||||
}
|
||||
|
||||
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
|
||||
info!("Get idle rate for {:?}", id);
|
||||
None
|
||||
}
|
||||
}
|
2
src/hid/mod.rs
Normal file
2
src/hid/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod gcc;
|
||||
pub mod procon;
|
762
src/hid/procon.rs
Normal file
762
src/hid/procon.rs
Normal file
|
@ -0,0 +1,762 @@
|
|||
///
|
||||
/// The majority of the logic in this file is derived from HOJA-LIB-RP2040
|
||||
/// https://github.com/HandHeldLegend/HOJA-LIB-RP2040
|
||||
///
|
||||
/// The original author gave their consent for this to be included in the NaxGCC firmware,
|
||||
/// and for this file to be distributed under the GPLv3, see https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/26
|
||||
///
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use defmt::{info, trace, Format};
|
||||
use embassy_rp::clocks::RoscRng;
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
|
||||
use embassy_time::Instant;
|
||||
use embassy_usb::{
|
||||
class::hid::{ReportId, RequestHandler},
|
||||
control::OutResponse,
|
||||
};
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
use rand::RngCore;
|
||||
|
||||
use crate::usb_comms::HidReportBuilder;
|
||||
|
||||
use super::gcc::GcState;
|
||||
|
||||
const SW_INFO_SET_MAC: u8 = 0x01;
|
||||
|
||||
const SW_CMD_SET_INPUT_MODE: u8 = 0x03;
|
||||
const SW_CMD_GET_DEVINFO: u8 = 0x02;
|
||||
const SW_CMD_SET_SHIPMODE: u8 = 0x08;
|
||||
const SW_CMD_GET_SPI: u8 = 0x10;
|
||||
const SW_CMD_SET_PAIRING: u8 = 0x01;
|
||||
const SW_CMD_GET_TRIGGERET: u8 = 0x04;
|
||||
|
||||
const ACK_GET_DEVINFO: u8 = 0x82;
|
||||
const ACK_GET_SPI: u8 = 0x90;
|
||||
const ACK_SET_PAIRING: u8 = 0x81;
|
||||
const ACK_GET_TRIGERET: u8 = 0x83;
|
||||
|
||||
const ACK_GENERIC: u8 = 0x80;
|
||||
|
||||
const RM_SEND_STATE: u8 = 0x30;
|
||||
|
||||
const PRO_CONTROLLER_STRING: [u8; 24] = [
|
||||
0x00, 0x25, 0x08, 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65,
|
||||
0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68,
|
||||
];
|
||||
|
||||
#[derive(Debug, Format, Clone, Copy)]
|
||||
struct ProconRequestInfo {
|
||||
report_id: ProconRequestId,
|
||||
response_id: ProconResponseId,
|
||||
command_id: u8,
|
||||
raw_data: [u8; 64],
|
||||
}
|
||||
|
||||
#[derive(Debug, Format, Clone, Copy)]
|
||||
enum ProconRequestId {
|
||||
GetInfo = 0x80,
|
||||
Command = 0x01,
|
||||
Rumble = 0x10,
|
||||
}
|
||||
|
||||
#[derive(Debug, Format, Clone, Copy)]
|
||||
enum ProconResponseId {
|
||||
GetInfo = 0x81,
|
||||
GetState = 0x30,
|
||||
Command = 0x21,
|
||||
}
|
||||
|
||||
static SIGNAL_PROCON_REQUEST: Signal<CriticalSectionRawMutex, ProconRequestInfo> = Signal::new();
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const PROCON_REPORT_DESCRIPTOR: &[u8] = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
|
||||
0x09, 0x04, // Usage (Joystick)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
|
||||
0x85, 0x30, // Report ID (48)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x0A, // Usage Maximum (0x0A)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x0A, // Report Count (10)
|
||||
0x55, 0x00, // Unit Exponent (0)
|
||||
0x65, 0x00, // Unit (None)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x0B, // Usage Minimum (0x0B)
|
||||
0x29, 0x0E, // Usage Maximum (0x0E)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
|
||||
0x0B, 0x01, 0x00, 0x01, 0x00, // Usage (0x010001)
|
||||
0xA1, 0x00, // Collection (Physical)
|
||||
0x0B, 0x30, 0x00, 0x01, 0x00, // Usage (0x010030)
|
||||
0x0B, 0x31, 0x00, 0x01, 0x00, // Usage (0x010031)
|
||||
0x0B, 0x32, 0x00, 0x01, 0x00, // Usage (0x010032)
|
||||
0x0B, 0x35, 0x00, 0x01, 0x00, // Usage (0x010035)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
|
||||
0x0B, 0x39, 0x00, 0x01, 0x00, // Usage (0x010039)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x07, // Logical Maximum (7)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x3B, 0x01, // Physical Maximum (315)
|
||||
0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x0F, // Usage Minimum (0x0F)
|
||||
0x29, 0x12, // Usage Maximum (0x12)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x34, // Report Count (52)
|
||||
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
|
||||
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
0x85, 0x21, // Report ID (33)
|
||||
0x09, 0x01, // Usage (0x01)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
|
||||
0x85, 0x81, // Report ID (-127)
|
||||
0x09, 0x02, // Usage (0x02)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x09, 0x03, // Usage (0x03)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
|
||||
|
||||
0x85, 0x10, // Report ID (16)
|
||||
0x09, 0x04, // Usage (0x04)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
|
||||
|
||||
0x85, 0x80, // Report ID (-128)
|
||||
0x09, 0x05, // Usage (0x05)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
|
||||
|
||||
0x85, 0x82, // Report ID (-126)
|
||||
0x09, 0x06, // Usage (0x06)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
|
||||
|
||||
0xC0, // End Collection
|
||||
|
||||
// 203 bytes
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Format)]
|
||||
struct ProconByteReport([u8; 64]);
|
||||
|
||||
impl Deref for ProconByteReport {
|
||||
type Target = [u8; 64];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ProconByteReport {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ProconByteReport {
|
||||
fn set_report_id(&mut self, id: u8) {
|
||||
self[0] = id;
|
||||
}
|
||||
|
||||
fn set_battery_status(&mut self) {
|
||||
self[2] = BatteryStatus::default()
|
||||
.pack()
|
||||
.expect("Failed to pack fake procon battery status")[0];
|
||||
}
|
||||
|
||||
fn set_timer(&mut self) {
|
||||
self[1] = Instant::now().as_millis() as u8;
|
||||
}
|
||||
|
||||
fn set_ack(&mut self, ack: u8) {
|
||||
self[13] = ack;
|
||||
}
|
||||
|
||||
fn set_subcommand(&mut self, cmd: u8) {
|
||||
self[14] = cmd;
|
||||
}
|
||||
|
||||
fn set_devinfo(&mut self) {
|
||||
self[15] = 0x04; // NS Firmware primary (4.x)
|
||||
self[16] = 0x33; // NS Firmware secondary (x.21)
|
||||
|
||||
self[17] = 0x03; // Controller ID primary (Pro Controller)
|
||||
self[18] = 0x02; // Controller ID secondary
|
||||
|
||||
self[25] = 0x01;
|
||||
self[26] = 0x02;
|
||||
}
|
||||
|
||||
fn sw_spi_readfromaddress(
|
||||
&mut self,
|
||||
offset_address: u8,
|
||||
address: u8,
|
||||
length: u8,
|
||||
switch_host_address: &[u8],
|
||||
) {
|
||||
let read_info = [address, offset_address, 0x00, 0x00, length];
|
||||
self[15..(15 + read_info.len())].copy_from_slice(&read_info);
|
||||
|
||||
let mut output_spi_data = [0u8; 30];
|
||||
|
||||
output_spi_data.iter_mut().enumerate().for_each(|(i, e)| {
|
||||
*e = sw_spi_getaddressdata(offset_address, address + i as u8, switch_host_address)
|
||||
});
|
||||
|
||||
self[20..(20 + length as usize)].copy_from_slice(&output_spi_data[..(length as usize)]);
|
||||
}
|
||||
|
||||
fn set_trigerret(&mut self, time_10_ms: u16) {
|
||||
let [upper_ms, lower_ms] = time_10_ms.to_be_bytes();
|
||||
|
||||
for i in 0..14 {
|
||||
self[15 + i] = upper_ms;
|
||||
self[16 + i] = lower_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sw_spi_getaddressdata(offset_address: u8, address: u8, switch_host_address: &[u8]) -> u8 {
|
||||
match offset_address {
|
||||
0x00 => 0x00,
|
||||
0x20..=0x40 => match address {
|
||||
0x26 | 0x00 => 0x95,
|
||||
// Size of pairing data
|
||||
0x27 | 0x01 => 0x22,
|
||||
// Checksum
|
||||
0x28 | 0x29 | 0x02 | 0x03 => 0x00,
|
||||
// Host BT address (Big-endian)
|
||||
0x2A..=0x2F => switch_host_address[(address - 0x2a) as usize],
|
||||
0x04..=0x09 => switch_host_address[(address - 4) as usize],
|
||||
// Bluetooth LTK (Little-endian) NOT IMPLEMENTED YET
|
||||
0x30..=0x3F => 0x00,
|
||||
0x0A..=0x19 => 0x00,
|
||||
// Host capability 0x68 is Nintendo Switch. 0x08 is PC
|
||||
0x4A | 0x24 => 0x68,
|
||||
0x4B | 0x25 => 0,
|
||||
_ => 0x00,
|
||||
},
|
||||
0x50 => 0x00,
|
||||
0x60 => match address {
|
||||
0x00..0x0f => 0xff,
|
||||
0x12 => 0x03,
|
||||
0x13 => 0x02,
|
||||
0x1b => 0x01,
|
||||
0x20 => 35,
|
||||
0x21 => 0,
|
||||
0x22 => 185,
|
||||
0x23 => 255,
|
||||
0x24 => 26,
|
||||
0x25 => 1,
|
||||
0x26 => 0,
|
||||
0x27 => 64,
|
||||
0x28 => 0,
|
||||
0x29 => 64,
|
||||
0x2A => 0,
|
||||
0x2B => 64,
|
||||
0x2C => 1,
|
||||
0x2D => 0,
|
||||
0x2E => 1,
|
||||
0x2F => 0,
|
||||
0x30 => 1,
|
||||
0x31 => 0,
|
||||
0x32 => 0x3B,
|
||||
0x33 => 0x34,
|
||||
0x34 => 0x3B,
|
||||
0x35 => 0x34,
|
||||
0x36 => 0x3B,
|
||||
0x37 => 0x34,
|
||||
0x3d..=0x45 => mk_switch_analog_calibration_data()[(address - 0x3d) as usize],
|
||||
0x46..=0x4e => mk_switch_analog_calibration_data()[(address - 0x3d) as usize],
|
||||
0x4F => 0xFF,
|
||||
0x50 => 26,
|
||||
0x51 => 26,
|
||||
0x52 => 26,
|
||||
0x53..=0x55 => 94,
|
||||
0x56 => 255,
|
||||
0x57 => 255,
|
||||
0x58 => 255,
|
||||
0x59..=0x5B => 255,
|
||||
0x5C => 0x01,
|
||||
0x80 => 80,
|
||||
0x81 => 253,
|
||||
0x82 => 0,
|
||||
0x83 => 0,
|
||||
0x84 => 198,
|
||||
0x85 => 15,
|
||||
0x98 | 0x86 => 15,
|
||||
0x99 | 0x87 => 48,
|
||||
0x9A | 0x88 => 97,
|
||||
0x9B | 0x89 => 174,
|
||||
0x9C | 0x8A => 144,
|
||||
0x9D | 0x8B => 217,
|
||||
0x9E | 0x8C => 212,
|
||||
0x9F | 0x8D => 20,
|
||||
0xA0 | 0x8E => 84,
|
||||
0xA1 | 0x8F => 65,
|
||||
0xA2 | 0x90 => 21,
|
||||
0xA3 | 0x91 => 84,
|
||||
0xA4 | 0x92 => 199,
|
||||
0xA5 | 0x93 => 121,
|
||||
0xA6 | 0x94 => 156,
|
||||
0xA7 | 0x95 => 51,
|
||||
0xA8 | 0x96 => 54,
|
||||
0xA9 | 0x97 => 99,
|
||||
_ => 0,
|
||||
},
|
||||
0x80 => match address {
|
||||
0x10..=0x1a => 0xff,
|
||||
0x1b..=0x25 => 0xff,
|
||||
0x26..=0x3f => 0xff,
|
||||
_ => 0xff,
|
||||
},
|
||||
_ => 0xff,
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_switch_analog_calibration_data() -> [u8; 18] {
|
||||
fn switch_analog_encode(in_lower: u16, in_upper: u16) -> [u8; 3] {
|
||||
let mut out = [0u8; 3];
|
||||
|
||||
[out[0], out[1]] = in_lower.to_le_bytes();
|
||||
|
||||
out[1] |= ((in_upper & 0xf) << 4) as u8;
|
||||
out[2] = ((in_upper & 0xff0) >> 4) as u8;
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
const MIN: u16 = 128 << 4;
|
||||
const MAXX: u16 = 128 << 4;
|
||||
const CENTER: u16 = 128 << 4;
|
||||
|
||||
let mut out = [0u8; 18];
|
||||
|
||||
out[0..3].copy_from_slice(&switch_analog_encode(MAXX, MAXX));
|
||||
out[3..6].copy_from_slice(&switch_analog_encode(CENTER, CENTER));
|
||||
out[6..9].copy_from_slice(&switch_analog_encode(MIN, MIN));
|
||||
|
||||
out[9..12].copy_from_slice(&switch_analog_encode(CENTER, CENTER));
|
||||
out[12..15].copy_from_slice(&switch_analog_encode(MIN, MIN));
|
||||
out[15..18].copy_from_slice(&switch_analog_encode(MAXX, MAXX));
|
||||
|
||||
info!("Returning switch data: {:x}", out);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct ProconButtonsRight {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_y: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_x: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_b: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_a: bool,
|
||||
#[packed_field(bits = "4")]
|
||||
pub trigger_r_sr: bool,
|
||||
#[packed_field(bits = "5")]
|
||||
pub trigger_r_sl: bool,
|
||||
#[packed_field(bits = "6")]
|
||||
pub trigger_r: bool,
|
||||
#[packed_field(bits = "7")]
|
||||
pub trigger_zr: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct ProconButtonsShared {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_minus: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_plus: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_sb_right: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_sb_left: bool,
|
||||
#[packed_field(bits = "4")]
|
||||
pub button_home: bool,
|
||||
#[packed_field(bits = "5")]
|
||||
pub button_capture: bool,
|
||||
#[packed_field(bits = "6")]
|
||||
pub none: bool,
|
||||
#[packed_field(bits = "7")]
|
||||
pub change_grip_active: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct ProconButtonsLeft {
|
||||
#[packed_field(bits = "0")]
|
||||
pub dpad_down: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub dpad_up: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub dpad_right: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub dped_left: bool,
|
||||
#[packed_field(bits = "4")]
|
||||
pub trigger_l_sr: bool,
|
||||
#[packed_field(bits = "5")]
|
||||
pub trigger_l_sl: bool,
|
||||
#[packed_field(bits = "6")]
|
||||
pub trigger_l: bool,
|
||||
#[packed_field(bits = "7")]
|
||||
pub trigger_zl: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "9")]
|
||||
pub struct ProconState {
|
||||
#[packed_field(bits = "0..=7")]
|
||||
pub buttons_right: ProconButtonsRight,
|
||||
#[packed_field(bits = "8..=15")]
|
||||
pub buttons_shared: ProconButtonsShared,
|
||||
#[packed_field(bits = "16..=23")]
|
||||
pub buttons_left: ProconButtonsLeft,
|
||||
#[packed_field(bits = "24..=39")]
|
||||
pub lstick_x: u16,
|
||||
#[packed_field(bits = "40..=47")]
|
||||
pub lstick_y: u8,
|
||||
#[packed_field(bits = "48..=63")]
|
||||
pub rstick_x: u16,
|
||||
#[packed_field(bits = "64..=71")]
|
||||
pub rstick_y: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", endian = "msb", size_bytes = "1")]
|
||||
struct BatteryStatus {
|
||||
#[packed_field(bits = "0..=3")]
|
||||
connection: u8,
|
||||
#[packed_field(bits = "4..=7")]
|
||||
battery_level: u8,
|
||||
}
|
||||
|
||||
impl Default for BatteryStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
connection: 1,
|
||||
battery_level: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GcState> for ProconState {
|
||||
fn from(value: &GcState) -> Self {
|
||||
Self {
|
||||
buttons_left: ProconButtonsLeft {
|
||||
dpad_down: value.buttons_1.dpad_down,
|
||||
dpad_right: value.buttons_1.dpad_right,
|
||||
dpad_up: value.buttons_1.dpad_up,
|
||||
dped_left: value.buttons_1.dpad_left,
|
||||
trigger_l: value.buttons_2.button_l,
|
||||
trigger_zl: value.buttons_2.button_l,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_right: ProconButtonsRight {
|
||||
button_a: value.buttons_1.button_a,
|
||||
button_b: value.buttons_1.button_b,
|
||||
button_x: value.buttons_1.button_x,
|
||||
button_y: value.buttons_1.button_y,
|
||||
trigger_r: value.buttons_2.button_z,
|
||||
trigger_zr: value.buttons_2.button_r,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_shared: ProconButtonsShared {
|
||||
button_plus: value.buttons_2.button_start && !value.buttons_2.button_z,
|
||||
button_home: value.buttons_2.button_start && value.buttons_2.button_z,
|
||||
..Default::default()
|
||||
},
|
||||
lstick_x: value.stick_x as u16 * 16,
|
||||
lstick_y: value.stick_y,
|
||||
rstick_x: value.cstick_x as u16 * 16,
|
||||
rstick_y: value.cstick_y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProconReportBuilder {
|
||||
switch_reporting_mode: u8,
|
||||
switch_mac_address: [u8; 6],
|
||||
switch_host_address: [u8; 6],
|
||||
switch_ltk: [u8; 16],
|
||||
}
|
||||
|
||||
impl Default for ProconReportBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
switch_reporting_mode: 0,
|
||||
switch_mac_address: gen_switch_mac_address(),
|
||||
switch_host_address: [0u8; 6],
|
||||
switch_ltk: gen_switch_ltk(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_switch_mac_address() -> [u8; 6] {
|
||||
let mut mac_addr = [0u8; 6];
|
||||
|
||||
mac_addr.iter_mut().for_each(|e| {
|
||||
*e = RoscRng.next_u64() as u8;
|
||||
});
|
||||
|
||||
mac_addr[0] &= 0xfe;
|
||||
mac_addr[5] = 0x9b;
|
||||
|
||||
mac_addr
|
||||
}
|
||||
|
||||
fn gen_switch_ltk() -> [u8; 16] {
|
||||
let mut switch_ltk = [0u8; 16];
|
||||
|
||||
switch_ltk.iter_mut().for_each(|e| {
|
||||
*e = RoscRng.next_u64() as u8;
|
||||
});
|
||||
|
||||
switch_ltk
|
||||
}
|
||||
|
||||
impl ProconReportBuilder {
|
||||
fn get_info_report(&self, current_report_info: &ProconRequestInfo) -> [u8; 64] {
|
||||
let mut report = ProconByteReport([0u8; 64]);
|
||||
|
||||
report.set_report_id(ProconResponseId::GetInfo as u8);
|
||||
|
||||
if current_report_info.command_id == SW_INFO_SET_MAC {
|
||||
report[1] = SW_INFO_SET_MAC;
|
||||
report[3] = 0x03;
|
||||
|
||||
self.switch_mac_address
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.for_each(|(i, e)| {
|
||||
report[4 + i] = *e;
|
||||
});
|
||||
} else {
|
||||
report[1] = current_report_info.command_id;
|
||||
}
|
||||
|
||||
*report
|
||||
}
|
||||
|
||||
fn get_state_report(&self, state: &ProconState) -> [u8; 64] {
|
||||
static mut UNKNOWN: u8 = 0xA;
|
||||
|
||||
unsafe {
|
||||
UNKNOWN = match UNKNOWN {
|
||||
0xA => 0xB,
|
||||
0xB => 0xC,
|
||||
_ => 0xA,
|
||||
};
|
||||
}
|
||||
|
||||
let mut report = ProconByteReport([0u8; 64]);
|
||||
|
||||
let data = state
|
||||
.pack()
|
||||
.expect("Failed to pack pro controller input data");
|
||||
|
||||
report.set_report_id(ProconResponseId::GetState as u8);
|
||||
report.set_timer();
|
||||
report.set_battery_status();
|
||||
|
||||
report[3..=11].copy_from_slice(&data);
|
||||
report[12] = unsafe { UNKNOWN };
|
||||
|
||||
*report
|
||||
}
|
||||
|
||||
fn get_command_report(&mut self, current_report_info: &ProconRequestInfo) -> [u8; 64] {
|
||||
let mut report = ProconByteReport([0u8; 64]);
|
||||
|
||||
report.set_report_id(ProconResponseId::Command as u8);
|
||||
report.set_timer();
|
||||
report.set_battery_status();
|
||||
report.set_subcommand(current_report_info.command_id);
|
||||
|
||||
match current_report_info.command_id {
|
||||
SW_CMD_SET_INPUT_MODE => {
|
||||
report.set_ack(ACK_GENERIC);
|
||||
self.switch_reporting_mode = current_report_info.raw_data[11];
|
||||
info!(
|
||||
"Switch reporting mode is now {:x}",
|
||||
self.switch_reporting_mode
|
||||
);
|
||||
}
|
||||
SW_CMD_GET_DEVINFO => {
|
||||
report.set_ack(ACK_GET_DEVINFO);
|
||||
report.set_devinfo();
|
||||
}
|
||||
SW_CMD_GET_SPI => {
|
||||
report.set_ack(ACK_GET_SPI);
|
||||
report.sw_spi_readfromaddress(
|
||||
current_report_info.raw_data[12],
|
||||
current_report_info.raw_data[11],
|
||||
current_report_info.raw_data[15],
|
||||
&self.switch_host_address,
|
||||
);
|
||||
}
|
||||
SW_CMD_SET_SHIPMODE => {
|
||||
report.set_ack(ACK_GENERIC);
|
||||
}
|
||||
SW_CMD_SET_PAIRING => {
|
||||
report.set_ack(ACK_SET_PAIRING);
|
||||
self.perform_pairing(&mut report, current_report_info);
|
||||
}
|
||||
SW_CMD_GET_TRIGGERET => {
|
||||
report.set_ack(ACK_GET_TRIGERET);
|
||||
report.set_trigerret(100);
|
||||
}
|
||||
_ => {
|
||||
report.set_ack(ACK_GENERIC);
|
||||
}
|
||||
}
|
||||
|
||||
*report
|
||||
}
|
||||
|
||||
fn perform_pairing(
|
||||
&mut self,
|
||||
report: &mut ProconByteReport,
|
||||
current_report_info: &ProconRequestInfo,
|
||||
) {
|
||||
let pairing_phase = current_report_info.raw_data[11];
|
||||
let host_address = ¤t_report_info.raw_data[12..];
|
||||
|
||||
match pairing_phase {
|
||||
1 => {
|
||||
self.switch_host_address
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, e)| *e = host_address[5 - i]);
|
||||
|
||||
report[16..=21].copy_from_slice(&self.switch_mac_address);
|
||||
|
||||
report[22..(22 + PRO_CONTROLLER_STRING.len())]
|
||||
.copy_from_slice(&PRO_CONTROLLER_STRING);
|
||||
}
|
||||
2 => {
|
||||
report[15] = 2;
|
||||
report[16..(16 + self.switch_ltk.len())].copy_from_slice(&self.switch_ltk);
|
||||
}
|
||||
3 => {
|
||||
report[15] = 3;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HidReportBuilder<64> for ProconReportBuilder {
|
||||
async fn get_hid_report(&mut self, state: &GcState) -> [u8; 64] {
|
||||
let current_report_info = if self.switch_reporting_mode == RM_SEND_STATE {
|
||||
SIGNAL_PROCON_REQUEST.try_take()
|
||||
} else {
|
||||
Some(SIGNAL_PROCON_REQUEST.wait().await)
|
||||
};
|
||||
|
||||
if let Some(current_report_info) = current_report_info {
|
||||
match current_report_info.report_id {
|
||||
ProconRequestId::GetInfo => self.get_info_report(¤t_report_info),
|
||||
ProconRequestId::Command => self.get_command_report(¤t_report_info),
|
||||
ProconRequestId::Rumble => self.get_state_report(&ProconState::from(state)),
|
||||
}
|
||||
} else {
|
||||
self.get_state_report(&ProconState::from(state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProconRequestHandler;
|
||||
|
||||
impl RequestHandler for ProconRequestHandler {
|
||||
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
|
||||
info!("Get report for {:?}", id);
|
||||
None
|
||||
}
|
||||
|
||||
fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
|
||||
trace!("Set report for {:?}: {:x}", id, data);
|
||||
|
||||
let mut buf = [0u8; 64];
|
||||
let len_to_copy = buf.len().min(data.len());
|
||||
buf[..len_to_copy].copy_from_slice(&data[..len_to_copy]);
|
||||
|
||||
if let ReportId::Out(id) = id {
|
||||
if id == ProconRequestId::GetInfo as u8 {
|
||||
SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo {
|
||||
command_id: buf[1],
|
||||
report_id: ProconRequestId::GetInfo,
|
||||
response_id: ProconResponseId::GetInfo,
|
||||
raw_data: buf,
|
||||
});
|
||||
} else if id == ProconRequestId::Command as u8 {
|
||||
SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo {
|
||||
command_id: buf[10],
|
||||
report_id: ProconRequestId::Command,
|
||||
response_id: ProconResponseId::Command,
|
||||
raw_data: buf,
|
||||
});
|
||||
} else if id == ProconRequestId::Rumble as u8 {
|
||||
// TODO: handle rumble
|
||||
}
|
||||
}
|
||||
|
||||
OutResponse::Accepted
|
||||
}
|
||||
|
||||
fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
|
||||
info!("Set idle rate for {:?} to {:?}", id, dur);
|
||||
}
|
||||
|
||||
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
|
||||
info!("Get idle rate for {:?}", id);
|
||||
None
|
||||
}
|
||||
}
|
105
src/input.rs
105
src/input.rs
|
@ -1,7 +1,7 @@
|
|||
use defmt::{debug, info, trace, Format};
|
||||
use embassy_futures::yield_now;
|
||||
use embassy_rp::{
|
||||
gpio::{AnyPin, Input, Output, Pin},
|
||||
gpio::{Input, Output},
|
||||
peripherals::SPI0,
|
||||
spi::{Blocking, Spi},
|
||||
};
|
||||
|
@ -16,19 +16,20 @@ use libm::{fmaxf, fminf};
|
|||
|
||||
use crate::{
|
||||
config::{
|
||||
ControllerConfig, InputConsistencyMode, OverrideGcReportInstruction, OverrideStickState,
|
||||
SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
|
||||
ControllerConfig, ControllerMode, InputConsistencyMode, OverrideGcReportInstruction,
|
||||
OverrideStickState, SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
|
||||
SIGNAL_OVERRIDE_STICK_STATE,
|
||||
},
|
||||
filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS},
|
||||
gcc_hid::{GcReport, MUTEX_INPUT_CONSISTENCY_MODE},
|
||||
helpers::XyValuePair,
|
||||
hid::gcc::GcState,
|
||||
input_filter::{DummyFilter, InputFilter},
|
||||
stick::{linearize, notch_remap, StickParams},
|
||||
usb_comms::{MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE},
|
||||
};
|
||||
|
||||
/// Used to send the button state to the usb task and the calibration task
|
||||
pub static CHANNEL_GCC_STATE: PubSubChannel<CriticalSectionRawMutex, GcReport, 1, 4, 1> =
|
||||
pub static CHANNEL_GCC_STATE: PubSubChannel<CriticalSectionRawMutex, GcState, 1, 4, 1> =
|
||||
PubSubChannel::new();
|
||||
|
||||
/// Used to send the stick state from the stick task to the main input task
|
||||
|
@ -36,10 +37,8 @@ static SIGNAL_STICK_STATE: Signal<CriticalSectionRawMutex, StickState> = Signal:
|
|||
|
||||
pub static SPI_SHARED: Mutex<ThreadModeRawMutex, Option<Spi<'static, SPI0, Blocking>>> =
|
||||
Mutex::new(None);
|
||||
pub static SPI_ACS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
|
||||
Mutex::new(None);
|
||||
pub static SPI_CCS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
|
||||
Mutex::new(None);
|
||||
pub static SPI_ACS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static>>> = Mutex::new(None);
|
||||
pub static SPI_CCS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static>>> = Mutex::new(None);
|
||||
|
||||
const STICK_HYST_VAL: f32 = 0.3;
|
||||
pub const FLOAT_ORIGIN: f32 = 127.5;
|
||||
|
@ -84,18 +83,12 @@ pub enum StickAxis {
|
|||
|
||||
#[inline(never)]
|
||||
#[link_section = ".time_critical.read_ext_adc"]
|
||||
pub fn read_ext_adc<
|
||||
'a,
|
||||
Acs: Pin,
|
||||
Ccs: Pin,
|
||||
I: embassy_rp::spi::Instance,
|
||||
M: embassy_rp::spi::Mode,
|
||||
>(
|
||||
pub fn read_ext_adc<'a, I: embassy_rp::spi::Instance, M: embassy_rp::spi::Mode>(
|
||||
which_stick: Stick,
|
||||
which_axis: StickAxis,
|
||||
spi: &mut Spi<'a, I, M>,
|
||||
spi_acs: &mut Output<'a, Acs>,
|
||||
spi_ccs: &mut Output<'a, Ccs>,
|
||||
spi_acs: &mut Output<'a>,
|
||||
spi_ccs: &mut Output<'a>,
|
||||
) -> u16 {
|
||||
let mut buf = [0b11010000; 3];
|
||||
|
||||
|
@ -352,19 +345,19 @@ async fn update_stick_states(
|
|||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_button_states(
|
||||
gcc_state: &mut GcReport,
|
||||
btn_a: &Input<'_, AnyPin>,
|
||||
btn_b: &Input<'_, AnyPin>,
|
||||
btn_x: &Input<'_, AnyPin>,
|
||||
btn_y: &Input<'_, AnyPin>,
|
||||
btn_start: &Input<'_, AnyPin>,
|
||||
btn_l: &Input<'_, AnyPin>,
|
||||
btn_r: &Input<'_, AnyPin>,
|
||||
btn_z: &Input<'_, AnyPin>,
|
||||
btn_dleft: &Input<'_, AnyPin>,
|
||||
btn_dright: &Input<'_, AnyPin>,
|
||||
btn_dup: &Input<'_, AnyPin>,
|
||||
btn_ddown: &Input<'_, AnyPin>,
|
||||
gcc_state: &mut GcState,
|
||||
btn_a: &Input<'_>,
|
||||
btn_b: &Input<'_>,
|
||||
btn_x: &Input<'_>,
|
||||
btn_y: &Input<'_>,
|
||||
btn_start: &Input<'_>,
|
||||
btn_l: &Input<'_>,
|
||||
btn_r: &Input<'_>,
|
||||
btn_z: &Input<'_>,
|
||||
btn_dleft: &Input<'_>,
|
||||
btn_dright: &Input<'_>,
|
||||
btn_dup: &Input<'_>,
|
||||
btn_ddown: &Input<'_>,
|
||||
) {
|
||||
gcc_state.buttons_1.button_a = btn_a.is_low();
|
||||
gcc_state.buttons_1.button_b = btn_b.is_low();
|
||||
|
@ -393,7 +386,7 @@ pub async fn input_integrity_benchmark() {
|
|||
loop {
|
||||
SIGNAL_OVERRIDE_GCC_STATE.signal(OverrideGcReportInstruction {
|
||||
report: {
|
||||
let mut report = GcReport::default();
|
||||
let mut report = GcState::default();
|
||||
report.buttons_1.dpad_up = true;
|
||||
report
|
||||
},
|
||||
|
@ -409,18 +402,18 @@ pub async fn input_integrity_benchmark() {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[embassy_executor::task]
|
||||
pub async fn update_button_state_task(
|
||||
btn_z: Input<'static, AnyPin>,
|
||||
btn_a: Input<'static, AnyPin>,
|
||||
btn_b: Input<'static, AnyPin>,
|
||||
btn_dright: Input<'static, AnyPin>,
|
||||
btn_dup: Input<'static, AnyPin>,
|
||||
btn_ddown: Input<'static, AnyPin>,
|
||||
btn_dleft: Input<'static, AnyPin>,
|
||||
btn_l: Input<'static, AnyPin>,
|
||||
btn_r: Input<'static, AnyPin>,
|
||||
btn_x: Input<'static, AnyPin>,
|
||||
btn_y: Input<'static, AnyPin>,
|
||||
btn_start: Input<'static, AnyPin>,
|
||||
btn_z: Input<'static>,
|
||||
btn_a: Input<'static>,
|
||||
btn_b: Input<'static>,
|
||||
btn_dright: Input<'static>,
|
||||
btn_dup: Input<'static>,
|
||||
btn_ddown: Input<'static>,
|
||||
btn_dleft: Input<'static>,
|
||||
btn_l: Input<'static>,
|
||||
btn_r: Input<'static>,
|
||||
btn_x: Input<'static>,
|
||||
btn_y: Input<'static>,
|
||||
btn_start: Input<'static>,
|
||||
) {
|
||||
// upon loop entry, we check for the reset combo once
|
||||
if btn_a.is_low() && btn_x.is_low() && btn_y.is_low() {
|
||||
|
@ -431,6 +424,15 @@ pub async fn update_button_state_task(
|
|||
loop {}
|
||||
}
|
||||
|
||||
{
|
||||
let mut m = MUTEX_CONTROLLER_MODE.lock().await;
|
||||
*m = if btn_start.is_low() {
|
||||
Some(ControllerMode::Procon)
|
||||
} else {
|
||||
Some(ControllerMode::GcAdapter)
|
||||
};
|
||||
}
|
||||
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
@ -438,9 +440,9 @@ pub async fn update_button_state_task(
|
|||
MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let mut previous_state = GcReport::default();
|
||||
let mut previous_state = GcState::default();
|
||||
|
||||
let mut gcc_state = GcReport::default();
|
||||
let mut gcc_state = GcState::default();
|
||||
|
||||
let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
|
||||
|
||||
|
@ -546,10 +548,13 @@ pub async fn update_button_state_task(
|
|||
#[link_section = ".time_critical.update_stick_states_task"]
|
||||
pub async fn update_stick_states_task(
|
||||
spi: Spi<'static, SPI0, embassy_rp::spi::Blocking>,
|
||||
spi_acs: Output<'static, AnyPin>,
|
||||
spi_ccs: Output<'static, AnyPin>,
|
||||
spi_acs: Output<'static>,
|
||||
spi_ccs: Output<'static>,
|
||||
) {
|
||||
Timer::after_secs(1).await;
|
||||
// let some time pass before accepting stick inputs
|
||||
// to ensure sticks are properly zeroed
|
||||
Timer::after_secs(2).await;
|
||||
|
||||
*SPI_SHARED.lock().await = Some(spi);
|
||||
*SPI_ACS_SHARED.lock().await = Some(spi_acs);
|
||||
*SPI_CCS_SHARED.lock().await = Some(spi_ccs);
|
||||
|
@ -608,7 +613,7 @@ pub async fn update_stick_states_task(
|
|||
let n = Instant::now();
|
||||
|
||||
match (n - last_loop_time).as_micros() {
|
||||
a if a > 1666 => debug!("Loop took {} us", a),
|
||||
a if a > 800 => debug!("Loop took {} us", a),
|
||||
_ => {}
|
||||
};
|
||||
last_loop_time = n;
|
||||
|
|
|
@ -2,7 +2,7 @@ use defmt::warn;
|
|||
|
||||
use crate::{
|
||||
config::{is_awaitable_button_pressed, AwaitableButtons},
|
||||
gcc_hid::GcReport,
|
||||
hid::gcc::GcState,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
|||
*/
|
||||
|
||||
pub trait InputFilter: Sized {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcReport);
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcState);
|
||||
}
|
||||
|
||||
/// Presses a single button if another button is pressed.
|
||||
|
@ -26,7 +26,7 @@ pub struct SingleButtonMacroFilter {
|
|||
}
|
||||
|
||||
impl InputFilter for SingleButtonMacroFilter {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcReport) {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcState) {
|
||||
if is_awaitable_button_pressed(gcc_state, &self.btn_instigator) {
|
||||
match self.btn_to_press {
|
||||
AwaitableButtons::A => {
|
||||
|
@ -83,7 +83,7 @@ impl InputFilter for SingleButtonMacroFilter {
|
|||
pub struct CStickUpTiltFilter;
|
||||
|
||||
impl InputFilter for CStickUpTiltFilter {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcReport) {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcState) {
|
||||
if gcc_state.cstick_y > 157 {
|
||||
if (137..=201).contains(&gcc_state.cstick_x) {
|
||||
gcc_state.cstick_x = 201;
|
||||
|
@ -110,7 +110,7 @@ impl InputFilter for CStickUpTiltFilter {
|
|||
pub struct CStickAngledFTiltFilter;
|
||||
|
||||
impl InputFilter for CStickAngledFTiltFilter {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcReport) {
|
||||
fn apply_filter(&mut self, gcc_state: &mut GcState) {
|
||||
if gcc_state.cstick_y > 147 {
|
||||
if (147..=225).contains(&gcc_state.cstick_x) {
|
||||
gcc_state.cstick_x = 205;
|
||||
|
@ -136,5 +136,5 @@ impl InputFilter for CStickAngledFTiltFilter {
|
|||
pub struct DummyFilter;
|
||||
|
||||
impl InputFilter for DummyFilter {
|
||||
fn apply_filter(&mut self, _gcc_state: &mut GcReport) {}
|
||||
fn apply_filter(&mut self, _gcc_state: &mut GcState) {}
|
||||
}
|
||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -1,19 +1,18 @@
|
|||
//! This example test the RP Pico on board LED.
|
||||
//!
|
||||
//! It does not work with the RP Pico W board. See wifi_blinky.rs.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
mod config;
|
||||
mod filter;
|
||||
mod gcc_hid;
|
||||
mod helpers;
|
||||
mod hid;
|
||||
mod input;
|
||||
mod input_filter;
|
||||
mod stick;
|
||||
mod usb_comms;
|
||||
|
||||
use core::ptr::addr_of_mut;
|
||||
|
||||
use config::config_task;
|
||||
use defmt::{debug, info};
|
||||
use defmt::info;
|
||||
use embassy_executor::Executor;
|
||||
use embassy_rp::{
|
||||
bind_interrupts,
|
||||
|
@ -24,14 +23,14 @@ use embassy_rp::{
|
|||
spi::{self, Spi},
|
||||
usb::{Driver, InterruptHandler},
|
||||
};
|
||||
use gcc_hid::usb_transfer_task;
|
||||
use gpio::{Level, Output};
|
||||
use usb_comms::usb_transfer_task;
|
||||
|
||||
use input::{update_button_state_task, update_stick_states_task};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::config::enter_config_mode_task;
|
||||
use crate::gcc_hid::rumble_task;
|
||||
use crate::usb_comms::rumble_task;
|
||||
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
|
@ -75,34 +74,37 @@ fn main() -> ! {
|
|||
let spi_acs = Output::new(AnyPin::from(p_acs), Level::High); // active low
|
||||
let spi_ccs = Output::new(AnyPin::from(p_ccs), Level::High); // active low
|
||||
|
||||
spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
|
||||
let executor1 = EXECUTOR1.init(Executor::new());
|
||||
debug!("Mana");
|
||||
executor1.run(|spawner| {
|
||||
spawner.spawn(usb_transfer_task(uid, driver)).unwrap();
|
||||
spawner.spawn(enter_config_mode_task()).unwrap();
|
||||
spawner
|
||||
.spawn(rumble_task(p.PIN_25, p.PIN_29, p.PWM_CH4, p.PWM_CH6))
|
||||
.unwrap();
|
||||
// spawner.spawn(input_integrity_benchmark()).unwrap();
|
||||
spawner
|
||||
.spawn(update_button_state_task(
|
||||
Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_17), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_16), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_11), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_9), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_10), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_8), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_22), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_21), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_18), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_19), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_5), gpio::Pull::Up),
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
spawn_core1(
|
||||
p.CORE1,
|
||||
unsafe { addr_of_mut!(CORE1_STACK).as_mut().unwrap() },
|
||||
move || {
|
||||
let executor1 = EXECUTOR1.init(Executor::new());
|
||||
executor1.run(|spawner| {
|
||||
spawner.spawn(usb_transfer_task(uid, driver)).unwrap();
|
||||
spawner.spawn(enter_config_mode_task()).unwrap();
|
||||
spawner
|
||||
.spawn(rumble_task(p.PIN_25, p.PIN_29, p.PWM_SLICE4, p.PWM_SLICE6))
|
||||
.unwrap();
|
||||
// spawner.spawn(input_integrity_benchmark()).unwrap();
|
||||
spawner
|
||||
.spawn(update_button_state_task(
|
||||
Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_17), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_16), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_11), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_9), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_10), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_8), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_22), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_21), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_18), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_19), gpio::Pull::Up),
|
||||
Input::new(AnyPin::from(p.PIN_5), gpio::Pull::Up),
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let executor0 = EXECUTOR0.init(Executor::new());
|
||||
info!("Initialized.");
|
||||
|
|
|
@ -2,30 +2,36 @@
|
|||
* Communication with the console / PC over USB HID.
|
||||
* Includes the HID report descriptor, and the GcReport struct.
|
||||
*/
|
||||
use core::default::Default;
|
||||
use core::{default::Default, future::Future};
|
||||
|
||||
use defmt::{debug, info, trace, warn, Format};
|
||||
use defmt::{debug, info, trace, warn};
|
||||
use embassy_futures::join::join;
|
||||
use embassy_rp::{
|
||||
peripherals::{PIN_25, PIN_29, PWM_CH4, PWM_CH6, USB},
|
||||
peripherals::{PIN_25, PIN_29, PWM_SLICE4, PWM_SLICE6, USB},
|
||||
pwm::Pwm,
|
||||
usb::Driver,
|
||||
usb::Driver as EmbassyDriver,
|
||||
};
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
use embassy_usb::{
|
||||
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
||||
control::OutResponse,
|
||||
class::hid::{HidReader, HidReaderWriter, HidWriter, RequestHandler, State},
|
||||
driver::Driver,
|
||||
msos::{self, windows_version},
|
||||
Builder, Handler,
|
||||
Builder, Handler, UsbDevice,
|
||||
};
|
||||
use libm::powf;
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
|
||||
use crate::{config::InputConsistencyMode, input::CHANNEL_GCC_STATE};
|
||||
use crate::{
|
||||
config::{ControllerMode, InputConsistencyMode},
|
||||
hid::{
|
||||
gcc::{GcReportBuilder, GcState, GccRequestHandler, GCC_REPORT_DESCRIPTOR},
|
||||
procon::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR},
|
||||
},
|
||||
input::CHANNEL_GCC_STATE,
|
||||
};
|
||||
|
||||
static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
|
||||
pub static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
|
||||
|
||||
/// We could turn the config change signal into a PubSubChannel instead, but that
|
||||
/// would just transmit unnecessary amounts of data.
|
||||
|
@ -37,197 +43,40 @@ pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
|
|||
Option<InputConsistencyMode>,
|
||||
> = Mutex::new(None);
|
||||
|
||||
/// Only dispatched ONCE after powerup, to determine how to advertise itself via USB.
|
||||
pub static MUTEX_CONTROLLER_MODE: Mutex<CriticalSectionRawMutex, Option<ControllerMode>> =
|
||||
Mutex::new(None);
|
||||
|
||||
/// Vendor-defined property data
|
||||
const DEVICE_INTERFACE_GUID: &str = "{ecceff35-146c-4ff3-acd9-8f992d09acdd}";
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x05, // Usage (Game Pad)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x11, // Report ID (17)
|
||||
0x19, 0x00, // Usage Minimum (Undefined)
|
||||
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x21, // Report ID (33)
|
||||
0x05, 0x00, // Usage Page (Undefined)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0xFF, // Logical Maximum (-1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x08, // Usage Maximum (0x08)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x09, 0x32, // Usage (Z)
|
||||
0x09, 0x33, // Usage (Rx)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x15, 0x81, // Logical Minimum (-127)
|
||||
0x25, 0x7F, // Logical Maximum (127)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
0xA1, 0x03, // Collection (Report)
|
||||
0x85, 0x13, // Report ID (19)
|
||||
0x19, 0x00, // Usage Minimum (Undefined)
|
||||
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct Buttons1 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_a: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_b: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_x: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_y: bool,
|
||||
#[packed_field(bits = "4")]
|
||||
pub dpad_left: bool,
|
||||
#[packed_field(bits = "5")]
|
||||
pub dpad_right: bool,
|
||||
#[packed_field(bits = "6")]
|
||||
pub dpad_down: bool,
|
||||
#[packed_field(bits = "7")]
|
||||
pub dpad_up: bool,
|
||||
pub trait HidReportBuilder<const LEN: usize> {
|
||||
async fn get_hid_report(&mut self, state: &GcState) -> [u8; LEN];
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct Buttons2 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_start: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
pub button_z: bool,
|
||||
#[packed_field(bits = "2")]
|
||||
pub button_r: bool,
|
||||
#[packed_field(bits = "3")]
|
||||
pub button_l: bool,
|
||||
#[packed_field(bits = "4..=7")]
|
||||
pub blank1: u8,
|
||||
struct UsbConfig {
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
report_descriptor: &'static [u8],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
|
||||
pub struct GcReport {
|
||||
#[packed_field(bits = "0..=7")]
|
||||
pub buttons_1: Buttons1,
|
||||
#[packed_field(bits = "8..=15")]
|
||||
pub buttons_2: Buttons2,
|
||||
#[packed_field(bits = "16..=23")]
|
||||
pub stick_x: u8,
|
||||
#[packed_field(bits = "24..=31")]
|
||||
pub stick_y: u8,
|
||||
#[packed_field(bits = "32..=39")]
|
||||
pub cstick_x: u8,
|
||||
#[packed_field(bits = "40..=47")]
|
||||
pub cstick_y: u8,
|
||||
#[packed_field(bits = "48..=55")]
|
||||
pub trigger_l: u8,
|
||||
#[packed_field(bits = "56..=63")]
|
||||
pub trigger_r: u8,
|
||||
}
|
||||
|
||||
impl Default for GcReport {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buttons_1: Buttons1::default(),
|
||||
buttons_2: Buttons2::default(),
|
||||
stick_x: 127,
|
||||
stick_y: 127,
|
||||
cstick_x: 127,
|
||||
cstick_y: 127,
|
||||
trigger_l: 0,
|
||||
trigger_r: 0,
|
||||
impl From<ControllerMode> for UsbConfig {
|
||||
fn from(value: ControllerMode) -> Self {
|
||||
match value {
|
||||
ControllerMode::GcAdapter => Self {
|
||||
vid: 0x057e,
|
||||
pid: 0x0337,
|
||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||
},
|
||||
ControllerMode::Procon => Self {
|
||||
vid: 0x57e,
|
||||
pid: 0x2009,
|
||||
report_descriptor: PROCON_REPORT_DESCRIPTOR,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(C, align(8))]
|
||||
pub struct RawConsoleReport {
|
||||
pub packet: [u8; 64],
|
||||
}
|
||||
|
||||
impl Default for RawConsoleReport {
|
||||
fn default() -> Self {
|
||||
Self { packet: [0u8; 64] }
|
||||
}
|
||||
}
|
||||
|
||||
struct GccRequestHandler {}
|
||||
|
||||
impl RequestHandler for GccRequestHandler {
|
||||
fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
|
||||
info!("Get report for {:?}", id);
|
||||
None
|
||||
}
|
||||
|
||||
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
|
||||
info!("Set report for {:?}: {=[u8]}", id, data);
|
||||
OutResponse::Accepted
|
||||
}
|
||||
|
||||
fn set_idle_ms(&self, id: Option<ReportId>, dur: u32) {
|
||||
info!("Set idle rate for {:?} to {:?}", id, dur);
|
||||
}
|
||||
|
||||
fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
|
||||
info!("Get idle rate for {:?}", id);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_gcinput_hid_report(input_state: &GcReport) -> [u8; 37] {
|
||||
static mut GC_FIRST: bool = false;
|
||||
|
||||
let mut buffer = [0u8; 37];
|
||||
|
||||
buffer[0] = 0x21;
|
||||
buffer[1] |= 0x14;
|
||||
|
||||
let data = input_state.pack().expect("Failed to pack GC input data");
|
||||
|
||||
if unsafe { !GC_FIRST } {
|
||||
buffer[1] |= 0x04;
|
||||
buffer[10] |= 0x04;
|
||||
buffer[19] |= 0x04;
|
||||
buffer[28] |= 0x04;
|
||||
unsafe { GC_FIRST = true };
|
||||
} else {
|
||||
// controller in "port 1"
|
||||
buffer[2..=9].copy_from_slice(&data[0..=7]);
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
struct MyDeviceHandler {
|
||||
configured: bool,
|
||||
}
|
||||
|
@ -241,6 +90,9 @@ impl MyDeviceHandler {
|
|||
impl Handler for MyDeviceHandler {
|
||||
fn enabled(&mut self, enabled: bool) {
|
||||
self.configured = true;
|
||||
|
||||
// reason: in production, info! compiles to nothing
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
if enabled {
|
||||
info!("Device enabled");
|
||||
} else {
|
||||
|
@ -270,102 +122,60 @@ impl Handler for MyDeviceHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>) {
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let mut serial_buffer = [0u8; 64];
|
||||
|
||||
let serial = format_no_std::show(
|
||||
&mut serial_buffer,
|
||||
format_args!(
|
||||
"{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||
raw_serial[0],
|
||||
raw_serial[1],
|
||||
raw_serial[2],
|
||||
raw_serial[3],
|
||||
raw_serial[4],
|
||||
raw_serial[5],
|
||||
raw_serial[6],
|
||||
raw_serial[7]
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
info!("Detected flash with unique serial number {}", serial);
|
||||
|
||||
trace!("Start of config");
|
||||
let mut usb_config = embassy_usb::Config::new(0x057e, 0x0337);
|
||||
usb_config.manufacturer = Some("Naxdy");
|
||||
usb_config.product = Some(match input_consistency_mode {
|
||||
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
||||
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
|
||||
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
|
||||
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
|
||||
});
|
||||
usb_config.serial_number = Some(serial);
|
||||
usb_config.max_power = 200;
|
||||
usb_config.max_packet_size_0 = 64;
|
||||
usb_config.device_class = 0;
|
||||
usb_config.device_protocol = 0;
|
||||
usb_config.self_powered = false;
|
||||
usb_config.device_sub_class = 0;
|
||||
usb_config.supports_remote_wakeup = true;
|
||||
|
||||
let mut device_descriptor = [0; 256];
|
||||
let mut config_descriptor = [0; 256];
|
||||
let mut bos_descriptor = [0; 256];
|
||||
let mut msos_descriptor = [0; 256];
|
||||
let mut control_buf = [0; 64];
|
||||
|
||||
let request_handler = GccRequestHandler {};
|
||||
let mut device_handler = MyDeviceHandler::new();
|
||||
|
||||
let mut state = State::new();
|
||||
|
||||
let mut builder = Builder::new(
|
||||
driver,
|
||||
usb_config,
|
||||
&mut device_descriptor,
|
||||
&mut config_descriptor,
|
||||
&mut bos_descriptor,
|
||||
&mut msos_descriptor,
|
||||
&mut control_buf,
|
||||
);
|
||||
|
||||
builder.msos_descriptor(windows_version::WIN8_1, 2);
|
||||
|
||||
let msos_writer = builder.msos_writer();
|
||||
msos_writer.device_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
|
||||
msos_writer.device_feature(msos::RegistryPropertyFeatureDescriptor::new(
|
||||
"DeviceInterfaceGUID",
|
||||
msos::PropertyData::Sz(DEVICE_INTERFACE_GUID),
|
||||
));
|
||||
|
||||
builder.handler(&mut device_handler);
|
||||
|
||||
fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
|
||||
input_consistency_mode: InputConsistencyMode,
|
||||
report_descriptor: &'d [u8],
|
||||
mut builder: Builder<'d, D>,
|
||||
state: &'d mut State<'d>,
|
||||
) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) {
|
||||
let hid_config = embassy_usb::class::hid::Config {
|
||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||
request_handler: Some(&request_handler),
|
||||
report_descriptor,
|
||||
request_handler: None,
|
||||
poll_ms: match input_consistency_mode {
|
||||
InputConsistencyMode::Original => 8,
|
||||
InputConsistencyMode::ConsistencyHack
|
||||
| InputConsistencyMode::SuperHack
|
||||
| InputConsistencyMode::PC => 1,
|
||||
},
|
||||
max_packet_size_in: 37,
|
||||
max_packet_size_out: 5,
|
||||
max_packet_size_in: W as u16,
|
||||
max_packet_size_out: R as u16,
|
||||
};
|
||||
let hid = HidReaderWriter::<_, 5, 37>::new(&mut builder, &mut state, hid_config);
|
||||
|
||||
let mut usb = builder.build();
|
||||
let hid: HidReaderWriter<'d, D, R, W> =
|
||||
HidReaderWriter::<'_, D, R, W>::new(&mut builder, state, hid_config);
|
||||
|
||||
let usb_fut = async {
|
||||
let usb = builder.build();
|
||||
|
||||
let (reader, writer) = hid.split();
|
||||
|
||||
(usb, reader, writer)
|
||||
}
|
||||
|
||||
fn mk_usb_transfer_futures<'d, D, H, Rq, const R: usize, const W: usize>(
|
||||
input_consistency_mode: InputConsistencyMode,
|
||||
usb_config: &UsbConfig,
|
||||
request_handler: &'d mut Rq,
|
||||
builder: Builder<'d, D>,
|
||||
state: &'d mut State<'d>,
|
||||
mut hid_report_builder: H,
|
||||
) -> (
|
||||
impl Future<Output = ()> + 'd,
|
||||
impl Future<Output = ()> + 'd,
|
||||
impl Future<Output = ()> + 'd,
|
||||
)
|
||||
where
|
||||
D: Driver<'d> + 'd,
|
||||
H: HidReportBuilder<W> + 'd,
|
||||
Rq: RequestHandler,
|
||||
{
|
||||
let (mut usb, reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
|
||||
input_consistency_mode,
|
||||
usb_config.report_descriptor,
|
||||
builder,
|
||||
state,
|
||||
);
|
||||
|
||||
let usb_fut = async move {
|
||||
loop {
|
||||
usb.run_until_suspend().await;
|
||||
debug!("Suspended");
|
||||
|
@ -374,9 +184,7 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
}
|
||||
};
|
||||
|
||||
let (mut reader, mut writer) = hid.split();
|
||||
|
||||
let in_fut = async {
|
||||
let in_fut = async move {
|
||||
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
||||
|
||||
let mut last_report_time = Instant::now();
|
||||
|
@ -400,15 +208,15 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
|
||||
}
|
||||
|
||||
match writer
|
||||
.write(&{
|
||||
let state = gcc_subscriber.next_message_pure().await;
|
||||
let report = get_gcinput_hid_report(&state);
|
||||
trace!("Report Written: {:08b}", report);
|
||||
report
|
||||
})
|
||||
.await
|
||||
{
|
||||
writer.ready().await;
|
||||
|
||||
let state = gcc_subscriber.next_message_pure().await;
|
||||
|
||||
let report = hid_report_builder.get_hid_report(&state).await;
|
||||
|
||||
trace!("Writing report: {:08b}", report);
|
||||
|
||||
match writer.write(&report).await {
|
||||
Ok(()) => {
|
||||
let currtime = Instant::now();
|
||||
let polltime = currtime.duration_since(last_report_time);
|
||||
|
@ -428,20 +236,9 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
}
|
||||
};
|
||||
|
||||
let out_fut = async {
|
||||
loop {
|
||||
trace!("Readery loop");
|
||||
let mut buf = [0u8; 5];
|
||||
match reader.read(&mut buf).await {
|
||||
Ok(_e) => {
|
||||
debug!("READ SOMETHIN: {:08b}", buf);
|
||||
SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to read: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let out_fut = async move {
|
||||
trace!("Readery loop");
|
||||
reader.run(true, request_handler).await;
|
||||
};
|
||||
|
||||
let usb_fut_wrapped = async {
|
||||
|
@ -449,7 +246,125 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
debug!("USB FUT DED");
|
||||
};
|
||||
|
||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||
(usb_fut_wrapped, in_fut, out_fut)
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'static, USB>) {
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let controller_mode = {
|
||||
while MUTEX_CONTROLLER_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
MUTEX_CONTROLLER_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let config = UsbConfig::from(controller_mode);
|
||||
|
||||
let mut serial_buffer = [0u8; 64];
|
||||
|
||||
let serial = format_no_std::show(
|
||||
&mut serial_buffer,
|
||||
format_args!(
|
||||
"{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||
raw_serial[0],
|
||||
raw_serial[1],
|
||||
raw_serial[2],
|
||||
raw_serial[3],
|
||||
raw_serial[4],
|
||||
raw_serial[5],
|
||||
raw_serial[6],
|
||||
raw_serial[7]
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
info!("Detected flash with unique serial number {}", serial);
|
||||
|
||||
trace!("Start of config");
|
||||
let mut usb_config = embassy_usb::Config::new(config.vid, config.pid);
|
||||
usb_config.manufacturer = Some("Naxdy");
|
||||
usb_config.product = Some(match input_consistency_mode {
|
||||
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
||||
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
|
||||
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
|
||||
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
|
||||
});
|
||||
usb_config.serial_number = Some(serial);
|
||||
usb_config.max_power = 200;
|
||||
usb_config.max_packet_size_0 = 64;
|
||||
usb_config.device_class = 0;
|
||||
usb_config.device_protocol = 0;
|
||||
usb_config.self_powered = false;
|
||||
usb_config.device_sub_class = 0;
|
||||
usb_config.supports_remote_wakeup = true;
|
||||
|
||||
let mut config_descriptor = [0; 256];
|
||||
let mut bos_descriptor = [0; 256];
|
||||
let mut msos_descriptor = [0; 256];
|
||||
let mut control_buf = [0; 64];
|
||||
|
||||
let mut device_handler = MyDeviceHandler::new();
|
||||
|
||||
let mut state = State::new();
|
||||
|
||||
let mut builder = Builder::new(
|
||||
driver,
|
||||
usb_config,
|
||||
&mut config_descriptor,
|
||||
&mut bos_descriptor,
|
||||
&mut msos_descriptor,
|
||||
&mut control_buf,
|
||||
);
|
||||
|
||||
builder.msos_descriptor(windows_version::WIN8_1, 2);
|
||||
|
||||
let msos_writer = builder.msos_writer();
|
||||
msos_writer.device_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
|
||||
msos_writer.device_feature(msos::RegistryPropertyFeatureDescriptor::new(
|
||||
"DeviceInterfaceGUID",
|
||||
msos::PropertyData::Sz(DEVICE_INTERFACE_GUID),
|
||||
));
|
||||
|
||||
builder.handler(&mut device_handler);
|
||||
|
||||
match controller_mode {
|
||||
ControllerMode::GcAdapter => {
|
||||
let mut request_handler = GccRequestHandler;
|
||||
|
||||
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
|
||||
input_consistency_mode,
|
||||
&config,
|
||||
&mut request_handler,
|
||||
builder,
|
||||
&mut state,
|
||||
GcReportBuilder::default(),
|
||||
);
|
||||
|
||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||
}
|
||||
ControllerMode::Procon => {
|
||||
let mut request_handler = ProconRequestHandler;
|
||||
|
||||
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 64, 64>(
|
||||
input_consistency_mode,
|
||||
&config,
|
||||
&mut request_handler,
|
||||
builder,
|
||||
&mut state,
|
||||
ProconReportBuilder::default(),
|
||||
);
|
||||
|
||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn calc_rumble_power(strength: u8) -> u16 {
|
||||
|
@ -464,8 +379,8 @@ fn calc_rumble_power(strength: u8) -> u16 {
|
|||
pub async fn rumble_task(
|
||||
pin_rumble: PIN_25,
|
||||
pin_brake: PIN_29,
|
||||
pwm_ch_rumble: PWM_CH4,
|
||||
pwm_ch_brake: PWM_CH6,
|
||||
pwm_ch_rumble: PWM_SLICE4,
|
||||
pwm_ch_brake: PWM_SLICE6,
|
||||
) {
|
||||
let mut rumble_config: embassy_rp::pwm::Config = Default::default();
|
||||
rumble_config.top = 255;
|
Loading…
Reference in a new issue