feat: implement "Pro-Controller" mode (#26)
All checks were successful
Publish stable release / build (push) Successful in 7m38s
Publish nightly release / build (push) Successful in 10m3s

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:
Naxdy 2024-10-14 19:55:54 +00:00
parent 4e1af50ebd
commit 8f8d47704d
Signed by: git.naxdy.org
GPG key ID: 05379DCFAACD8AC2
15 changed files with 1505 additions and 631 deletions

12
.changelogs/v1.2.0.md Normal file
View 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.

View file

@ -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

View file

@ -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
View file

@ -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",
]

View file

@ -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" }

View file

@ -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",

View file

@ -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;
};
}));
}

View file

@ -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
View 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
View file

@ -0,0 +1,2 @@
pub mod gcc;
pub mod procon;

762
src/hid/procon.rs Normal file
View 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 = &current_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(&current_report_info),
ProconRequestId::Command => self.get_command_report(&current_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
}
}

View file

@ -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;

View file

@ -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) {}
}

View file

@ -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.");

View file

@ -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;