diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..a566c83 --- /dev/null +++ b/Justfile @@ -0,0 +1,30 @@ +set export := true + +default: check + +check: + cd rp2040 && cargo check --target thumbv6m-none-eabi + +test: + cd rp2040 && cargo test --lib --target x86_64-unknown-linux-gnu --features std + +build-uf2: + cd rp2040 && cargo build --release --target thumbv6m-none-eabi + cd rp2040 && cargo objcopy --release --target thumbv6m-none-eabi -- -O binary target/thumbv6m-none-eabi/release/cmdr-keyboard-42.bin + cd rp2040 && python3 uf2conv.py target/thumbv6m-none-eabi/release/cmdr-keyboard-42.bin --base 0x10000000 --family 0xe48bff56 --convert --output target/firmware.uf2 + +clean: + cargo clean --manifest-path rp2040/Cargo.toml + +flash mount="" timeout="10": + @just build-uf2 + MOUNT="{{mount}}" python3 tools/copy_uf2.py --source rp2040/target/firmware.uf2 --timeout {{timeout}} + +flash-ssh target mount="/Volumes/RPI-RP2" key="" port="22": + @just build-uf2 + target="{{target}}" + mount="{{mount}}" + key_arg="" + if [ -n "{{key}}" ]; then key_arg="-i {{key}}"; fi + ssh $key_arg -p {{port}} "$target" "mkdir -p \"$mount\"" + scp $key_arg -P {{port}} rp2040/target/firmware.uf2 "$target:$mount/" diff --git a/README.md b/README.md index 1fdb155..7901e58 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,20 @@ _This HW(PCB) was original made for use with the TeensyLC module and is reused b - Function layer system with two Fn keys (40/43/44) enabling sticky lock, OS lock and deep modifier combos without ghosting. - High-speed matrix scanning (250 µs cadence) with scan-based debounce, per-key press spacing protection and idle/suspend rate throttling. - Bootloader entry options including `Fn+Fn+LCtrl+LShift+RShift` runtime chord and power-on detection on button index 0. -- Status LED heartbeat derived from joystick firmware: startup heartbeat, activity/idle transitions, sticky-state colors, Caps Lock flash, suspend blanking and error signalling. +- Status LED heartbeat carried over from the earlier CMDR firmware: startup heartbeat, activity/idle transitions, sticky-state colors, Caps Lock flash, suspend blanking and error signalling. - Power-aware USB handling that drops scan frequency 20× during suspend while honouring wake-on-input and immediate resume. -## Install script (`install.sh`) +## Development workflow -The repository includes a host automation helper for the RP2040 firmware. +Recurring tasks are defined in the root `Justfile`: -- Quick start: `./install.sh check` (compilation + clippy) or `./install.sh test` (full suite including host-side tests). -- Flashing: `./install.sh flash --local` for direct UF2 copy on Linux/macOS, or `./install.sh flash --ssh --target user@host --mount /path` for remote deployments. -- Cleaning: `./install.sh clean` removes cargo artefacts, generated binaries and UF2 images. -- Prerequisites are validated automatically (Rust toolchain, `thumbv6m-none-eabi`, `cargo-binutils`, `uf2conv.py`, optional SSH settings). -- Script output uses colour-coded sections for readability and fails fast on missing dependencies or connectivity issues. +- Build-check the embedded target: `just check` +- Run host-side tests: `just test` +- Remove build artifacts: `just clean` +- Build and copy a UF2 to a mounted RP2040: `just flash` (auto-detects `/Volumes/*`, `/media/*`, `/run/media/*`; override with `mount=/path` and optional `timeout=30` seconds) +- Build, then transfer a UF2 over SSH: `just flash-ssh target=user@host mount=/media/user/RPI-RP2` + +The flash recipes rely on `python3`, the bundled `rp2040/uf2conv.py`, `tools/copy_uf2.py`, and `cargo objcopy` (install with `rustup component add llvm-tools-preview` and `cargo install cargo-binutils`). For SSH transfers you also need passwordless access or supply a key path, e.g. `just flash-ssh target=user@host mount=/media/user/RPI-RP2 key=~/.ssh/id_ed25519`. ## Build environment rp2040 Zero diff --git a/install.sh b/install.sh deleted file mode 100755 index febb628..0000000 --- a/install.sh +++ /dev/null @@ -1,567 +0,0 @@ -#!/bin/bash - -# CMtec CMDR Joystick 25 Install Script -# Supports building, testing, and deploying RP2040 firmware - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Show usage information -show_usage() { - echo -e "${GREEN}CMtec CMDR Joystick 25 Install Script${NC}" - echo "========================================" - echo - echo "Usage: $0 [flash|test|check|clean] [options]" - echo - echo "Commands:" - echo " flash Build and flash RP2040 firmware" - echo " test Run comprehensive test suite" - echo " check Quick compilation and lint checks" - echo " clean Clean build artifacts and temp files" - echo - echo "flash options:" - echo " --local Build + flash locally (RP2040 mass storage)" - echo " --ssh Build + transfer UF2 via SSH" - echo " --mount PATH Remote RP2040 mount (default /Volumes/RPI-RP2)" - echo - echo "SSH options (when using --ssh):" - echo " --target user@host Combined user and host" - echo " --user USER SSH username" - echo " --host HOST SSH hostname or IP" - echo " --port PORT SSH port (default 22)" - echo " --key PATH SSH private key path" - echo - echo "Examples:" - echo " $0 # Show this help" - echo " $0 test # Run all tests" - echo " $0 check # Quick compilation check" - echo " $0 flash --local # Build and flash firmware locally" - echo " $0 flash --ssh --target user@host --mount /Volumes/RPI-RP2" - echo " $0 clean # Clean build artifacts" - exit "${1:-0}" -} - -# Parse command line arguments -COMMAND="${1:-}" -shift 1 || true - -# If no command provided, show usage -if [ -z "$COMMAND" ]; then - show_usage 0 -fi - -case "$COMMAND" in -flash | test | check | clean) ;; --h | --help | help) - show_usage 0 - ;; -*) - echo -e "${RED}Error: Unknown command '$COMMAND'${NC}" - echo - show_usage 1 - ;; -esac - -echo -e "${GREEN}CMtec CMDR Keyboard Install Script${NC}" -echo "========================================" -echo -e "${BLUE}Mode: $COMMAND${NC}" -echo - -# Get script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Ensure cargo-binutils is available for `cargo objcopy` -ensure_cargo_binutils() { - if ! command -v cargo-objcopy >/dev/null 2>&1; then - echo "Installing cargo-binutils and llvm-tools-preview (required for UF2 conversion)..." - rustup component add llvm-tools-preview || true - if ! cargo install cargo-binutils; then - echo -e "${RED}Error: Failed to install cargo-binutils${NC}" - echo "Install manually: rustup component add llvm-tools-preview && cargo install cargo-binutils" - exit 1 - fi - fi -} - -# Function to check prerequisites -check_prerequisites() { - echo "Checking prerequisites..." - - if ! command -v cargo &>/dev/null; then - echo -e "${RED}Error: cargo (Rust toolchain) not found${NC}" - echo "Install Rust: https://rustup.rs/" - exit 1 - fi - - if ! command -v python3 &>/dev/null; then - echo -e "${RED}Error: python3 not found${NC}" - echo "Install Python 3" - exit 1 - fi - - # Check for required Rust target - if ! rustup target list --installed | grep -q "thumbv6m-none-eabi"; then - echo "Installing Rust target thumbv6m-none-eabi..." - rustup target add thumbv6m-none-eabi - fi - - # Check for x86_64 target for testing - if ! rustup target list --installed | grep -q "x86_64-unknown-linux-gnu"; then - echo "Installing Rust target x86_64-unknown-linux-gnu (for testing)..." - rustup target add x86_64-unknown-linux-gnu - fi - - echo -e "${GREEN}✓${NC} Prerequisites check complete" - echo -} - -# Function to run comprehensive test suite -run_tests() { - echo "Running comprehensive test suite..." - echo - - check_prerequisites - - # Change to RP2040 directory - RP2040_DIR="$SCRIPT_DIR/rp2040" - if [ ! -d "$RP2040_DIR" ]; then - echo -e "${RED}Error: RP2040 directory not found at $RP2040_DIR${NC}" - exit 1 - fi - - cd "$RP2040_DIR" - - # Test 1: Cargo check for embedded target - echo "Testing embedded target compilation..." - if ! cargo check --target thumbv6m-none-eabi; then - echo -e "${RED}✗${NC} Embedded target compilation check failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Embedded target compilation check passed" - - # Test 2: Host target tests (17 comprehensive tests) - echo "Running host target test suite..." - if ! cargo test --lib --target x86_64-unknown-linux-gnu --features std; then - echo -e "${RED}✗${NC} Host target tests failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Host target tests passed (17 tests)" - - # Test 3: Clippy for code quality (warnings allowed for now) - echo "Running clippy code quality checks..." - if ! cargo clippy --target thumbv6m-none-eabi; then - echo -e "${RED}✗${NC} Clippy code quality check failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Clippy code quality check passed" - - # Test 4: Release build test - echo "Testing release build..." - if ! cargo build --release --target thumbv6m-none-eabi; then - echo -e "${RED}✗${NC} Release build failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Release build passed" - - cd "$SCRIPT_DIR" - - echo - echo -e "${GREEN}All tests completed successfully!${NC}" - echo - echo "Test summary:" - echo -e "${GREEN}✓${NC} Embedded target compilation check" - echo -e "${GREEN}✓${NC} Host target test suite (17 tests: expo + storage modules)" - echo -e "${GREEN}✓${NC} Clippy code quality checks" - echo -e "${GREEN}✓${NC} Release build verification" - echo - echo -e "${GREEN}Codebase is ready for deployment.${NC}" -} - -# Function to run quick checks -run_check() { - echo "Running quick compilation and lint checks..." - echo - - check_prerequisites - - # Change to RP2040 directory - RP2040_DIR="$SCRIPT_DIR/rp2040" - if [ ! -d "$RP2040_DIR" ]; then - echo -e "${RED}Error: RP2040 directory not found at $RP2040_DIR${NC}" - exit 1 - fi - - cd "$RP2040_DIR" - - # Quick cargo check - echo "Checking embedded target compilation..." - if ! cargo check --target thumbv6m-none-eabi; then - echo -e "${RED}✗${NC} Compilation check failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Compilation check passed" - - # Quick clippy check (warnings allowed for now) - echo "Running clippy..." - if ! cargo clippy --target thumbv6m-none-eabi; then - echo -e "${RED}✗${NC} Clippy check failed" - exit 1 - fi - echo -e "${GREEN}✓${NC} Clippy check passed" - - cd "$SCRIPT_DIR" - - echo - echo -e "${GREEN}Quick checks completed successfully!${NC}" -} - -# Function to clean build artifacts -clean_build() { - echo "Cleaning build artifacts and temporary files..." - echo - - RP2040_DIR="$SCRIPT_DIR/rp2040" - if [ ! -d "$RP2040_DIR" ]; then - echo -e "${RED}Error: RP2040 directory not found at $RP2040_DIR${NC}" - exit 1 - fi - - cd "$RP2040_DIR" - - # Clean cargo artifacts - echo "Cleaning cargo build artifacts..." - cargo clean - echo -e "${GREEN}✓${NC} Cargo artifacts cleaned" - - # Remove UF2 and binary files - if [ -f "firmware.uf2" ]; then - rm -f firmware.uf2 - echo -e "${GREEN}✓${NC} Removed firmware.uf2" - fi - - if [ -f "target/thumbv6m-none-eabi/release/cmdr-joystick-25.bin" ]; then - rm -f target/thumbv6m-none-eabi/release/cmdr-joystick-25.bin - echo -e "${GREEN}✓${NC} Removed binary files" - fi - - # Clean any temp files - rm -f /tmp/cmdr_*.tmp 2>/dev/null || true - - cd "$SCRIPT_DIR" - - echo - echo -e "${GREEN}Cleanup completed!${NC}" -} - -# Function to build firmware locally -build_firmware() { - RP2040_DIR="$SCRIPT_DIR/rp2040" - - if [ ! -d "$RP2040_DIR" ]; then - echo -e "${RED}Error: RP2040 directory not found at $RP2040_DIR${NC}" - exit 1 - fi - - check_prerequisites - - # Build firmware - echo "Building RP2040 firmware..." - cd "$RP2040_DIR" - - if ! cargo build --release --target thumbv6m-none-eabi; then - echo -e "${RED}Error: Failed to build firmware${NC}" - exit 1 - fi - - echo -e "${GREEN}✓${NC} Firmware build complete" - echo - - # Create UF2 file - echo "Converting to UF2 format..." - - ensure_cargo_binutils - if ! cargo objcopy --release --target thumbv6m-none-eabi -- -O binary target/thumbv6m-none-eabi/release/cmdr-joystick-25.bin; then - echo -e "${RED}Error: Failed to create binary file${NC}" - exit 1 - fi - - # Check for uf2conv.py script - if [ ! -f "uf2conv.py" ]; then - echo "Downloading uf2conv.py..." - if ! curl -L -o uf2conv.py "https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py"; then - echo -e "${RED}Error: Failed to download uf2conv.py${NC}" - exit 1 - fi - chmod +x uf2conv.py - fi - - if ! python3 uf2conv.py -b 0x10000000 -f 0xe48bff56 -c -o firmware.uf2 target/thumbv6m-none-eabi/release/cmdr-joystick-25.bin; then - echo -e "${RED}Error: Failed to convert to UF2 format${NC}" - exit 1 - fi - - echo -e "${GREEN}✓${NC} UF2 conversion complete" - echo -} - -# Function to build and transfer firmware via SSH -flash_firmware_ssh() { - RP2040_DIR="$SCRIPT_DIR/rp2040" - - if [ ! -d "$RP2040_DIR" ]; then - echo -e "${RED}Error: RP2040 directory not found at $RP2040_DIR${NC}" - exit 1 - fi - - # Parse CLI args for SSH - SSH_HOST="" - SSH_USER="" - SSH_PORT="22" - SSH_KEY="" - REMOTE_MOUNT="/Volumes/RPI-RP2" - while [ $# -gt 0 ]; do - case "$1" in - --target) - shift - TARGET="$1" - SSH_USER="${TARGET%@*}" - SSH_HOST="${TARGET#*@}" - ;; - --host) - shift - SSH_HOST="$1" - ;; - --user) - shift - SSH_USER="$1" - ;; - --port) - shift - SSH_PORT="$1" - ;; - --key) - shift - SSH_KEY="$1" - ;; - --mount) - shift - REMOTE_MOUNT="$1" - ;; - *) echo -e "${YELLOW}Warning: Unknown SSH option: $1${NC}" ;; - esac - shift || true - done - - if [ -z "$SSH_HOST" ] || [ -z "$SSH_USER" ]; then - echo -e "${RED}Error: provide SSH credentials via --target user@host or --user/--host${NC}" - exit 1 - fi - - # Build firmware locally first - build_firmware - - echo "Testing SSH connection to $SSH_USER@$SSH_HOST:$SSH_PORT..." - - mkdir -p "${HOME}/.ssh" - SSH_CONTROL_PATH="${HOME}/.ssh/cmdr_%C" - local SSH_OPTS=( - -p "$SSH_PORT" - -o "StrictHostKeyChecking=accept-new" - -o "ControlMaster=auto" - -o "ControlPersist=60s" - -o "ControlPath=$SSH_CONTROL_PATH" - -o "PreferredAuthentications=publickey,password,keyboard-interactive" - ) - if [ -n "$SSH_KEY" ]; then - SSH_OPTS+=(-i "$SSH_KEY") - fi - - # Test SSH connection - if ! ssh "${SSH_OPTS[@]}" -o ConnectTimeout=10 -fN "$SSH_USER@$SSH_HOST" 2>/dev/null; then - echo -e "${RED}Error: Cannot connect to $SSH_USER@$SSH_HOST:$SSH_PORT${NC}" - echo "Tips:" - echo "- Verify network reachability and SSH port" - echo "- Ensure SSH keys are properly configured" - exit 1 - fi - - echo -e "${GREEN}✓${NC} SSH connection successful" - - # Check if remote mount exists - echo "Checking remote RP2040 mount path: $REMOTE_MOUNT" - if ! ssh "${SSH_OPTS[@]}" "$SSH_USER@$SSH_HOST" "test -d \"$REMOTE_MOUNT\""; then - echo -e "${RED}Remote mount not found: $REMOTE_MOUNT${NC}" - echo -e "${YELLOW}Ensure the RP2040 is in BOOTSEL mode and mounted on the remote host.${NC}" - echo "Common paths: /Volumes/RPI-RP2 (macOS), /media/\$USER/RPI-RP2 (Linux)" - exit 1 - fi - - echo "Transferring firmware.uf2 to remote host..." - local SCP_OPTS=(-P "$SSH_PORT" -o "StrictHostKeyChecking=accept-new" -o "ControlPath=$SSH_CONTROL_PATH") - if [ -n "$SSH_KEY" ]; then - SCP_OPTS+=(-i "$SSH_KEY") - fi - - cd "$RP2040_DIR" - if ! scp "${SCP_OPTS[@]}" firmware.uf2 "$SSH_USER@$SSH_HOST:$REMOTE_MOUNT/firmware.uf2"; then - echo -e "${RED}✗${NC} Failed to transfer firmware to remote RP2040" - exit 1 - fi - - echo -e "${GREEN}✓${NC} Transferred firmware.uf2 to $SSH_USER@$SSH_HOST:$REMOTE_MOUNT" - - cd "$SCRIPT_DIR" - - echo - echo -e "${GREEN}SSH firmware deployment completed!${NC}" - echo "Remote: $SSH_USER@$SSH_HOST:$REMOTE_MOUNT/firmware.uf2" - echo -e "${YELLOW}The remote RP2040 should now restart with new firmware.${NC}" -} - -# Function to flash firmware locally -flash_firmware_local() { - build_firmware - - RP2040_DIR="$SCRIPT_DIR/rp2040" - cd "$RP2040_DIR" - - # Detect OS and set up mount detection - OS="$(uname -s)" - - # Wait for and flash to RP2040 - echo "Waiting for RP2040 in bootloader mode..." - echo -e "${YELLOW}Put your RP2040 into bootloader mode (hold BOOTSEL button while plugging in USB)${NC}" - - case "$OS" in - Linux*) - # Wait for RPI-RP2 mount point with timeout - MAX_WAIT=120 - waited=0 - while [ ! -d "/media/RPI-RP2" ] && [ ! -d "/media/$USER/RPI-RP2" ] && [ ! -d "/run/media/$USER/RPI-RP2" ]; do - sleep 1 - waited=$((waited + 1)) - if [ "$waited" -ge "$MAX_WAIT" ]; then - echo -e "${RED}Timeout waiting for RP2040 mount (RPI-RP2)${NC}" - echo "Ensure the device is in BOOTSEL mode and mounted (try replugging with BOOTSEL held)." - echo "Expected mount at one of: /media/RPI-RP2, /media/$USER/RPI-RP2, /run/media/$USER/RPI-RP2" - exit 1 - fi - done - - # Find the actual mount point - if [ -d "/media/RPI-RP2" ]; then - MOUNT_POINT="/media/RPI-RP2" - elif [ -d "/media/$USER/RPI-RP2" ]; then - MOUNT_POINT="/media/$USER/RPI-RP2" - elif [ -d "/run/media/$USER/RPI-RP2" ]; then - MOUNT_POINT="/run/media/$USER/RPI-RP2" - else - echo -e "${RED}Error: Could not find RPI-RP2 mount point${NC}" - exit 1 - fi - - echo -e "${GREEN}✓${NC} Found RP2040 at $MOUNT_POINT" - - # Flash firmware - echo "Flashing firmware..." - cp firmware.uf2 "$MOUNT_POINT/" - echo -e "${GREEN}✓${NC} Firmware flashed successfully!" - ;; - Darwin*) - # Wait for RPI-RP2 volume with timeout - MAX_WAIT=120 - waited=0 - while [ ! -d "/Volumes/RPI-RP2" ]; do - sleep 1 - waited=$((waited + 1)) - if [ "$waited" -ge "$MAX_WAIT" ]; then - echo -e "${RED}Timeout waiting for RP2040 mount at /Volumes/RPI-RP2${NC}" - echo "Ensure the device is in BOOTSEL mode and mounted (try replugging with BOOTSEL held)." - exit 1 - fi - done - - echo -e "${GREEN}✓${NC} Found RP2040 at /Volumes/RPI-RP2" - - # Flash firmware - echo "Flashing firmware..." - cp firmware.uf2 "/Volumes/RPI-RP2/" - echo -e "${GREEN}✓${NC} Firmware flashed successfully!" - ;; - *) - echo -e "${RED}Error: Unsupported operating system for local flashing${NC}" - echo "This script supports Linux and macOS for local flashing." - exit 1 - ;; - esac - - cd "$SCRIPT_DIR" - - echo - echo -e "${GREEN}Firmware installation completed!${NC}" - echo - echo "Firmware file: $RP2040_DIR/firmware.uf2" - echo -e "${GREEN}RP2040 firmware deployed successfully!${NC}" - echo -e "${YELLOW}The device should now restart with new firmware.${NC}" -} - -# Detect OS -case "$(uname -s)" in -Linux*) - OS="Linux" - ;; -Darwin*) - OS="macOS" - ;; -*) - echo -e "${RED}Error: Unsupported operating system${NC}" - echo "This script supports Linux and macOS only." - exit 1 - ;; -esac - -echo "Detected OS: $OS" -if [ "$COMMAND" = "flash" ]; then - echo "RP2040 firmware directory: $SCRIPT_DIR/rp2040" -elif [ "$COMMAND" = "test" ]; then - echo "Testing Rust embedded firmware with comprehensive test suite" -fi -echo - -# Execute command -case "$COMMAND" in -flash) - MODE="" - REM_ARGS=() - for arg in "$@"; do - case "$arg" in - --local) MODE="local" ;; - --ssh) MODE="ssh" ;; - *) REM_ARGS+=("$arg") ;; - esac - done - if [ "$MODE" = "local" ]; then - flash_firmware_local - elif [ "$MODE" = "ssh" ]; then - flash_firmware_ssh "${REM_ARGS[@]}" - else - echo -e "${RED}Error: specify --local or --ssh for flash${NC}" - show_usage 1 - fi - ;; -test) - run_tests - ;; -check) - run_check - ;; -clean) - clean_build - ;; -esac diff --git a/rp2040/.cargo/config.toml b/rp2040/.cargo/config.toml index e548cb6..6e13f73 100644 --- a/rp2040/.cargo/config.toml +++ b/rp2040/.cargo/config.toml @@ -24,7 +24,6 @@ target = "thumbv6m-none-eabi" rustflags = [ "-C", "link-arg=--nmagic", "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", "-C", "no-vectorize-loops", ] diff --git a/rp2040/Cargo.lock b/rp2040/Cargo.lock index caa2e76..e32d7fc 100644 --- a/rp2040/Cargo.lock +++ b/rp2040/Cargo.lock @@ -2,75 +2,21 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "ascii-canvas" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" -dependencies = [ - "term", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - [[package]] name = "bare-metal" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version 0.2.3", + "rustc_version", ] -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bitfield" version = "0.13.2" @@ -83,18 +29,6 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - [[package]] name = "bitvec" version = "1.0.1" @@ -107,15 +41,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bytemuck" version = "1.22.0" @@ -128,40 +53,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "cmdr-keyboard-42" version = "0.2.0" dependencies = [ "cortex-m", "cortex-m-rt", - "cortex-m-rtic", - "critical-section", - "defmt", - "defmt-rtt", - "dht-sensor", - "embedded-alloc", "embedded-hal 0.2.7", "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-hal-bus", - "embedded-io", "fugit", - "futures", - "hd44780-driver", - "heapless 0.8.0", - "nb 1.1.0", - "packed_struct", "panic-halt", - "panic-probe", - "pio 0.3.0", "portable-atomic", - "rp-binary-info 0.1.2", "rp2040-boot2", "rp2040-hal", "smart-leds", @@ -171,23 +73,13 @@ dependencies = [ "ws2812-pio", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "cortex-m" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ - "bare-metal 0.2.5", + "bare-metal", "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", @@ -213,43 +105,6 @@ dependencies = [ "syn 2.0.99", ] -[[package]] -name = "cortex-m-rtic" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d696ae7390bdb9f7978f71ca7144256a2c4616240a6df9002da3c451f9fc8f02" -dependencies = [ - "bare-metal 1.0.0", - "cortex-m", - "cortex-m-rtic-macros", - "heapless 0.7.17", - "rtic-core", - "rtic-monotonic", - "version_check", -] - -[[package]] -name = "cortex-m-rtic-macros" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eefb40b1ca901c759d29526e5c8a0a1b246c20caaa5b4cc5d0f0b94debecd4c7" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "rtic-syntax", - "syn 1.0.109", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc-any" version = "2.5.0" @@ -265,99 +120,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "debug-helper" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" -[[package]] -name = "defmt" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.99", -] - -[[package]] -name = "defmt-parser" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" -dependencies = [ - "thiserror", -] - -[[package]] -name = "defmt-rtt" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab697b3dbbc1750b7c8b821aa6f6e7f2480b47a99bc057a2ed7b170ebef0c51" -dependencies = [ - "critical-section", - "defmt", -] - -[[package]] -name = "dht-sensor" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d643854f324f02daea7153ebbfec5cfcc2f395faf1c7011be65e543570de28" -dependencies = [ - "embedded-hal 0.2.7", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "embedded-alloc" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" -dependencies = [ - "critical-section", - "linked_list_allocator", -] - [[package]] name = "embedded-dma" version = "0.2.0" @@ -382,9 +156,6 @@ name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" -dependencies = [ - "defmt", -] [[package]] name = "embedded-hal-async" @@ -392,23 +163,9 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ - "defmt", "embedded-hal 1.0.0", ] -[[package]] -name = "embedded-hal-bus" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3980bf28e8577db59fe2bdb3df868a419469d2cecb363644eea2b6f7797669" -dependencies = [ - "critical-section", - "defmt", - "embedded-hal 1.0.0", - "embedded-hal-async", - "portable-atomic", -] - [[package]] name = "embedded-hal-nb" version = "1.0.0" @@ -425,27 +182,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "frunk" version = "0.4.3" @@ -500,104 +236,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", -] - [[package]] name = "gcd" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - [[package]] name = "hash32" version = "0.3.1" @@ -607,70 +251,16 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "hd44780-driver" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab2b13fdeaed7dde9133a57c28b2cbde4a8fc8c3196b5631428aad114857d3a" -dependencies = [ - "embedded-hal 0.2.7", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32 0.2.1", - "rustc_version 0.4.1", - "spin", - "stable_deref_trait", -] - [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32 0.3.1", + "hash32", "stable_deref_trait", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", -] - [[package]] name = "itertools" version = "0.10.5" @@ -680,90 +270,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lalrpop" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.14.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "sha3", - "string_cache", - "term", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" -dependencies = [ - "regex-automata", - "rustversion", -] - -[[package]] -name = "libc" -version = "0.2.170" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" - -[[package]] -name = "linked_list_allocator" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - [[package]] name = "nb" version = "0.1.3" @@ -779,12 +285,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "num_enum" version = "0.5.11" @@ -858,82 +358,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" -[[package]] -name = "panic-probe" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" -dependencies = [ - "cortex-m", - "defmt", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap 2.7.1", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pio" version = "0.2.1" @@ -945,114 +375,11 @@ dependencies = [ "paste", ] -[[package]] -name = "pio" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ba4153cee9585abc451271aa437d9e8defdea8b468d48ba6b8f098cbe03d7f" -dependencies = [ - "pio-core", - "pio-proc", -] - -[[package]] -name = "pio-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" -dependencies = [ - "arrayvec", - "num_enum 0.7.3", - "paste", -] - -[[package]] -name = "pio-parser" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825266c1eaddf54f636d06eefa4bf3c99d774c14ec46a4a6c6e5128a0f10d205" -dependencies = [ - "lalrpop", - "lalrpop-util", - "pio-core", -] - -[[package]] -name = "pio-proc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4a76571f5fe51af43cc80ac870fe0c79cc0cdd686b9002a6c4c84bfdd0176b" -dependencies = [ - "codespan-reporting", - "lalrpop-util", - "pio-core", - "pio-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.99", -] - [[package]] name = "portable-atomic" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" -dependencies = [ - "critical-section", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.99", -] [[package]] name = "proc-macro2" @@ -1084,44 +411,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "redox_syscall" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rgb" version = "0.8.50" @@ -1137,11 +426,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ed2051a0bf2c726df01cfce378ed8a367be2a6e402fc183857f429a346d429" -[[package]] -name = "rp-binary-info" -version = "0.1.2" -source = "git+https://github.com/rp-rs/rp-hal/#01cdf556696a3286435dcff0d01b76ff4334eb06" - [[package]] name = "rp-hal-common" version = "0.1.0" @@ -1169,7 +453,6 @@ dependencies = [ "bitfield 0.14.0", "cortex-m", "critical-section", - "defmt", "embedded-dma", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -1178,12 +461,12 @@ dependencies = [ "embedded-io", "frunk", "fugit", - "itertools 0.10.5", + "itertools", "nb 1.1.0", "paste", - "pio 0.2.1", + "pio", "rand_core", - "rp-binary-info 0.1.1", + "rp-binary-info", "rp-hal-common", "rp2040-hal-macros", "rp2040-pac", @@ -1216,69 +499,15 @@ dependencies = [ "vcell", ] -[[package]] -name = "rtic-core" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42" - -[[package]] -name = "rtic-monotonic" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8b0b822d1a366470b9cea83a1d4e788392db763539dc4ba022bcc787fece82" - -[[package]] -name = "rtic-syntax" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5e215601dc467752c2bddc6284a622c6f3d2bab569d992adcd5ab7e4cb9478" -dependencies = [ - "indexmap 1.9.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", + "semver", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.26", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "semver" version = "0.9.0" @@ -1288,40 +517,12 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "smallvec" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" - [[package]] name = "smart-leds" version = "0.4.0" @@ -1349,15 +550,6 @@ dependencies = [ "rgb", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1366,25 +558,13 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_cell" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" dependencies = [ "portable-atomic", ] -[[package]] -name = "string_cache" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "syn" version = "1.0.109" @@ -1413,75 +593,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "term" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43bddab41f8626c7bdaab872bbba75f8df5847b516d77c569c746e2ae5eb746" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "usb-device" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ - "heapless 0.8.0", + "heapless", "portable-atomic", ] @@ -1493,7 +617,7 @@ checksum = "2d19fd35b28737afde3b854c0dc354df41fceada782f3add0a8115794eda2624" dependencies = [ "frunk", "fugit", - "heapless 0.8.0", + "heapless", "num_enum 0.7.3", "option-block", "packed_struct", @@ -1506,12 +630,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "void" version = "1.0.2" @@ -1527,98 +645,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "ws2812-pio" version = "0.9.0" @@ -1629,7 +655,7 @@ dependencies = [ "embedded-hal 0.2.7", "fugit", "nb 1.1.0", - "pio 0.2.1", + "pio", "rp2040-hal", "smart-leds-trait 0.2.1", "smart-leds-trait 0.3.1", diff --git a/rp2040/Cargo.toml b/rp2040/Cargo.toml index 4a2ece5..687aa2f 100644 --- a/rp2040/Cargo.toml +++ b/rp2040/Cargo.toml @@ -4,38 +4,18 @@ version = "0.2.0" edition = "2024" [dependencies] -# rp2040_hal dependencies copied from v0.11 cortex-m = "0.7.2" cortex-m-rt = "0.7" -cortex-m-rtic = "1.1.4" -critical-section = {version = "1.2.0"} -defmt = "0.3" -defmt-rtt = "0.4.0" -dht-sensor = "0.2.1" -embedded-alloc = "0.5.1" embedded-hal = "1.0.0" -embedded-hal-async = "1.0.0" -embedded-hal-bus = {version = "0.2.0", features = ["defmt-03"]} -embedded-io = "0.6.1" embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} fugit = "0.3.6" -futures = {version = "0.3.30", default-features = false, features = ["async-await"]} -hd44780-driver = "0.4.0" -nb = "1.0" panic-halt = "0.2.0" -panic-probe = {version = "0.3.1", features = ["print-defmt"]} -pio = "0.3.0" -portable-atomic = {version = "1.7.0", features = ["critical-section"]} rp2040-boot2 = "0.3.0" -rp2040-hal = {version = "0.11.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} -rp-binary-info = {git = "https://github.com/rp-rs/rp-hal/", version ="0.1.2"} -static_cell = "2.1.0" +rp2040-hal = {version = "0.11.0", features = ["binary-info", "critical-section-impl", "rt"]} # USB hid dependencies usbd-human-interface-device = {version = "0.5.1"} usb-device = "0.3" -packed_struct = { version = "0.10", default-features = false } -heapless = "0.8" # ws2812-pio dependencies ws2812-pio = "0.9.0" @@ -65,3 +45,7 @@ path = "src/main.rs" [features] default = [] std = [] + +[target.'cfg(target_arch = "arm")'.dependencies] +static_cell = "2.1.0" +portable-atomic = { version = "1.11.0", default-features = false, features = ["unsafe-assume-single-core"] } diff --git a/rp2040/src/board.rs b/rp2040/src/board.rs new file mode 100644 index 0000000..1b4d4ba --- /dev/null +++ b/rp2040/src/board.rs @@ -0,0 +1,137 @@ +use crate::{hardware, ButtonMatrix, MatrixPins, StatusLed}; +use cortex_m::delay::Delay; +use cortex_m::interrupt; +use rp2040_hal::{ + Clock, + clocks::init_clocks_and_plls, + gpio::Pins, + pac, + pio::PIOExt, + sio::Sio, + timer::Timer, + watchdog::Watchdog, +}; +use usb_device::class_prelude::UsbBusAllocator; + +pub type KeyboardMatrix = ButtonMatrix< + MatrixPins<{ hardware::KEY_ROWS }, { hardware::KEY_COLS }>, + { hardware::KEY_ROWS }, + { hardware::KEY_COLS }, + { hardware::NUMBER_OF_KEYS }, +>; + +pub type KeyboardStatusLed = StatusLed; + +pub struct Board { + pub button_matrix: KeyboardMatrix, + pub status_led: KeyboardStatusLed, + pub delay: Delay, + pub timer: Timer, + usb_bus: &'static UsbBusAllocator, +} + +pub struct BoardParts { + pub button_matrix: KeyboardMatrix, + pub status_led: KeyboardStatusLed, + pub delay: Delay, + pub timer: Timer, + pub usb_bus: &'static UsbBusAllocator, +} + +impl Board { + pub fn new() -> Self { + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + let mut watchdog = Watchdog::new(pac.WATCHDOG); + let clocks = init_clocks_and_plls( + hardware::XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let (rows, cols, status_pin) = hardware::split_board_pins(pins); + let matrix_pins = MatrixPins::new(rows, cols); + + let mut button_matrix = ButtonMatrix::new( + matrix_pins, + hardware::MATRIX_DEBOUNCE_SCANS_PRESS, + hardware::MATRIX_DEBOUNCE_SCANS_RELEASE, + hardware::MIN_PRESS_SPACING_SCANS, + ); + button_matrix.init_pins(); + + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let status_led_pin = status_pin; + let status_led = StatusLed::new( + status_led_pin, + &mut pio, + sm0, + clocks.peripheral_clock.freq(), + ); + + let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + let delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + let usb_bus = usb_allocator( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + &mut pac.RESETS, + ); + + Self { + button_matrix, + status_led, + delay, + timer, + usb_bus, + } + } + + pub fn into_parts(self) -> BoardParts { + BoardParts { + button_matrix: self.button_matrix, + status_led: self.status_led, + delay: self.delay, + timer: self.timer, + usb_bus: self.usb_bus, + } + } +} + +fn usb_allocator( + usbctrl_regs: pac::USBCTRL_REGS, + usbctrl_dpram: pac::USBCTRL_DPRAM, + usb_clock: rp2040_hal::clocks::UsbClock, + resets: &mut pac::RESETS, +) -> &'static UsbBusAllocator { + static USB_BUS: static_cell::StaticCell> = + static_cell::StaticCell::new(); + + interrupt::free(|_| { + USB_BUS.init_with(|| { + UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new( + usbctrl_regs, + usbctrl_dpram, + usb_clock, + true, + resets, + )) + }) + }) +} diff --git a/rp2040/src/bootloader.rs b/rp2040/src/bootloader.rs index f429449..79ca94b 100644 --- a/rp2040/src/bootloader.rs +++ b/rp2040/src/bootloader.rs @@ -1,6 +1,6 @@ //! Bootloader entry helpers shared between power-on checks and runtime combos. -use crate::{layout, status::StatusMode, StatusLed, NUMBER_OF_KEYS}; +use crate::{NUMBER_OF_KEYS, StatusLed, layout, status::StatusMode}; use cortex_m::asm; use rp2040_hal::{ gpio::AnyPin, diff --git a/rp2040/src/button_matrix.rs b/rp2040/src/button_matrix.rs index 32575e2..de49d5b 100644 --- a/rp2040/src/button_matrix.rs +++ b/rp2040/src/button_matrix.rs @@ -1,66 +1,115 @@ -//! Button matrix scanner for CMDR Keyboard 42 +//! Button matrix scanner for CMDR Keyboard 42. //! -//! Ported from the joystick firmware for consistent behaviour and structure. -//! Scans a row/column matrix and produces a debounced boolean state for each -//! button using per-button counters and small inter-column delays. +//! The scanner owns a concrete set of matrix pins and produces a debounced +//! boolean state for each key. -use core::convert::Infallible; use cortex_m::delay::Delay; use embedded_hal::digital::{InputPin, OutputPin}; +use rp2040_hal::gpio::{DynPinId, FunctionSioInput, FunctionSioOutput, Pin, PullNone, PullUp}; -/// Row/column scanned button matrix driver with debounce counters. -pub struct ButtonMatrix<'a, const R: usize, const C: usize, const N: usize> { - rows: &'a mut [&'a mut dyn InputPin; R], - cols: &'a mut [&'a mut dyn OutputPin; C], - pressed: [bool; N], - press_threshold: u8, - release_threshold: u8, - debounce_counter: [u8; N], - // Additional protection: minimum time between same-key presses - last_press_scan: [u32; N], - scan_counter: u32, +/// Abstraction over the physical row/column pins backing the button matrix. +pub trait MatrixPinAccess { + fn init_columns(&mut self); + fn set_column_low(&mut self, column: usize); + fn set_column_high(&mut self, column: usize); + fn read_row(&mut self, row: usize) -> bool; } -impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { +/// Concrete matrix pins built from RP2040 dynamic pins. +type RowPin = Pin; +type ColPin = Pin; + +pub struct MatrixPins { + rows: [RowPin; ROWS], + cols: [ColPin; COLS], +} + +impl MatrixPins { + pub fn new(rows: [RowPin; ROWS], cols: [ColPin; COLS]) -> Self { + Self { rows, cols } + } +} + +impl MatrixPinAccess for MatrixPins { + fn init_columns(&mut self) { + for column in self.cols.iter_mut() { + column.set_high().ok(); + } + } + + fn set_column_low(&mut self, column: usize) { + self.cols[column].set_low().ok(); + } + + fn set_column_high(&mut self, column: usize) { + self.cols[column].set_high().ok(); + } + + fn read_row(&mut self, row: usize) -> bool { + self.rows[row].is_low().unwrap_or(false) + } +} + +/// Row/column scanned button matrix driver with debounce counters. +pub struct ButtonMatrix { + pins: P, + pressed: [bool; KEYS], + press_threshold: u8, + release_threshold: u8, + debounce_counter: [u8; KEYS], + last_press_scan: [u32; KEYS], + scan_counter: u32, + min_press_gap_scans: u32, +} + +impl ButtonMatrix +where + P: MatrixPinAccess, +{ pub fn new( - rows: &'a mut [&'a mut dyn InputPin; R], - cols: &'a mut [&'a mut dyn OutputPin; C], + pins: P, press_threshold: u8, release_threshold: u8, + min_press_gap_scans: u32, ) -> Self { + debug_assert_eq!(KEYS, ROWS * COLS); Self { - rows, - cols, - pressed: [false; N], + pins, + pressed: [false; KEYS], press_threshold, release_threshold, - debounce_counter: [0; N], - last_press_scan: [0; N], + debounce_counter: [0; KEYS], + last_press_scan: [0; KEYS], scan_counter: 0, + min_press_gap_scans, } } pub fn init_pins(&mut self) { - for col in self.cols.iter_mut() { - col.set_high().unwrap(); + self.pins.init_columns(); + } + + pub fn prime(&mut self, delay: &mut Delay, passes: usize) { + for _ in 0..passes { + self.scan_matrix(delay); } } pub fn scan_matrix(&mut self, delay: &mut Delay) { self.scan_counter = self.scan_counter.wrapping_add(1); - for col_index in 0..self.cols.len() { - self.cols[col_index].set_low().unwrap(); + for column in 0..COLS { + self.pins.set_column_low(column); delay.delay_us(1); - self.process_column(col_index); - self.cols[col_index].set_high().unwrap(); + self.process_column(column); + self.pins.set_column_high(column); delay.delay_us(1); } } - fn process_column(&mut self, col_index: usize) { - for row_index in 0..self.rows.len() { - let button_index: usize = col_index + (row_index * C); - let current_state = self.rows[row_index].is_low().unwrap(); + pub(crate) fn process_column(&mut self, column: usize) { + for row in 0..ROWS { + let button_index = column + (row * COLS); + let current_state = self.pins.read_row(row); if current_state == self.pressed[button_index] { self.debounce_counter[button_index] = 0; @@ -77,147 +126,124 @@ impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, }; if self.debounce_counter[button_index] >= threshold { - // Additional protection for press events: minimum 20 scans (5ms) between presses - if current_state { // Pressing - let scans_since_last = self.scan_counter.wrapping_sub(self.last_press_scan[button_index]); - if scans_since_last >= 20 { // 5ms at 250μs scan rate - self.pressed[button_index] = current_state; + if current_state { + let elapsed = self + .scan_counter + .wrapping_sub(self.last_press_scan[button_index]); + if self.last_press_scan[button_index] == 0 + || elapsed >= self.min_press_gap_scans + { + self.pressed[button_index] = true; self.last_press_scan[button_index] = self.scan_counter; } - } else { // Releasing - self.pressed[button_index] = current_state; + } else { + self.pressed[button_index] = false; } self.debounce_counter[button_index] = 0; } } } - pub fn buttons_pressed(&mut self) -> [bool; N] { + pub fn buttons_pressed(&self) -> [bool; KEYS] { self.pressed } + + #[cfg(all(test, feature = "std"))] + pub(crate) fn set_scan_counter(&mut self, value: u32) { + self.scan_counter = value; + } + + #[cfg(all(test, feature = "std"))] + pub(crate) fn bump_scan_counter(&mut self) { + self.scan_counter = self.scan_counter.wrapping_add(1); + } } #[cfg(all(test, feature = "std"))] mod tests { use super::*; use core::cell::Cell; - use embedded_hal::digital::ErrorType; use std::rc::Rc; - struct MockInputPin { - state: Rc>, + #[derive(Clone)] + struct MockMatrixPins { + row: Rc>, + column: Rc>, } - impl MockInputPin { - fn new(state: Rc>) -> Self { - Self { state } + impl MockMatrixPins { + fn new(row: Rc>, column: Rc>) -> Self { + Self { row, column } } } - impl ErrorType for MockInputPin { - type Error = Infallible; - } - - impl InputPin for MockInputPin { - fn is_high(&mut self) -> Result { - Ok(!self.state.get()) + impl MatrixPinAccess<1, 1> for MockMatrixPins { + fn init_columns(&mut self) { + self.column.set(true); } - fn is_low(&mut self) -> Result { - Ok(self.state.get()) + fn set_column_low(&mut self, _column: usize) { + self.column.set(false); + } + + fn set_column_high(&mut self, _column: usize) { + self.column.set(true); + } + + fn read_row(&mut self, _row: usize) -> bool { + self.row.get() } } - struct MockOutputPin { - state: Rc>, - } - - impl MockOutputPin { - fn new(state: Rc>) -> Self { - Self { state } - } - } - - impl ErrorType for MockOutputPin { - type Error = Infallible; - } - - impl OutputPin for MockOutputPin { - fn set_high(&mut self) -> Result<(), Self::Error> { - self.state.set(true); - Ok(()) - } - - fn set_low(&mut self) -> Result<(), Self::Error> { - self.state.set(false); - Ok(()) - } - } - - fn matrix_fixture() -> ( - ButtonMatrix<'static, 1, 1, 1>, + fn fixture() -> ( + ButtonMatrix, Rc>, Rc>, ) { let row_state = Rc::new(Cell::new(false)); - let col_state = Rc::new(Cell::new(false)); - - let row_pin: &'static mut dyn InputPin = - Box::leak(Box::new(MockInputPin::new(row_state.clone()))); - let col_pin: &'static mut dyn OutputPin = - Box::leak(Box::new(MockOutputPin::new(col_state.clone()))); - - let rows: &'static mut [&'static mut dyn InputPin; 1] = - Box::leak(Box::new([row_pin])); - let cols: &'static mut [&'static mut dyn OutputPin; 1] = - Box::leak(Box::new([col_pin])); - - let matrix = ButtonMatrix::new(rows, cols, 5, 5); - - (matrix, row_state, col_state) + let column_state = Rc::new(Cell::new(false)); + let pins = MockMatrixPins::new(row_state.clone(), column_state.clone()); + let matrix = ButtonMatrix::new(pins, 5, 5, 200); + (matrix, row_state, column_state) } #[test] fn init_sets_columns_high() { - let (mut matrix, _row_state, col_state) = matrix_fixture(); - assert!(!col_state.get()); + let (mut matrix, _row, column) = fixture(); + assert!(!column.get()); matrix.init_pins(); - assert!(col_state.get()); + assert!(column.get()); } #[test] fn debounce_respects_threshold() { - let (mut matrix, row_state, _col_state) = matrix_fixture(); + let (mut matrix, row, _column) = fixture(); let mut states = matrix.buttons_pressed(); assert!(!states[0]); - // Set scan counter to start with enough history - matrix.scan_counter = 100; - - row_state.set(true); - // Need 5 scans to register press + matrix.set_scan_counter(100); + row.set(true); for _ in 0..4 { - matrix.scan_counter = matrix.scan_counter.wrapping_add(1); + matrix.bump_scan_counter(); matrix.process_column(0); states = matrix.buttons_pressed(); - assert!(!states[0]); // Still not pressed + assert!(!states[0]); } - matrix.scan_counter = matrix.scan_counter.wrapping_add(1); - matrix.process_column(0); // 5th scan + matrix.bump_scan_counter(); + matrix.process_column(0); states = matrix.buttons_pressed(); - assert!(states[0]); // Now pressed + assert!(states[0]); - row_state.set(false); - // Need 5 scans to register release + row.set(false); for _ in 0..4 { - matrix.scan_counter = matrix.scan_counter.wrapping_add(1); + matrix.bump_scan_counter(); matrix.process_column(0); states = matrix.buttons_pressed(); - assert!(states[0]); // Still pressed + assert!(states[0]); } - matrix.scan_counter = matrix.scan_counter.wrapping_add(1); - matrix.process_column(0); // 5th scan + matrix.bump_scan_counter(); + matrix.process_column(0); states = matrix.buttons_pressed(); - assert!(!states[0]); // Now released + assert!(!states[0]); } } diff --git a/rp2040/src/hardware.rs b/rp2040/src/hardware.rs index fdd57bc..badb40c 100644 --- a/rp2040/src/hardware.rs +++ b/rp2040/src/hardware.rs @@ -1,5 +1,8 @@ //! Hardware configuration and timing constants for the CMDR Keyboard 42. +use rp2040_hal::gpio::{self, DynPinId, FunctionPio0, FunctionSioInput, FunctionSioOutput, Pin, PullNone, PullUp}; +use rp2040_hal::gpio::Pins; + /// External crystal frequency (Hz). pub const XTAL_FREQ_HZ: u32 = 12_000_000; @@ -9,6 +12,9 @@ pub const XTAL_FREQ_HZ: u32 = 12_000_000; pub const MATRIX_DEBOUNCE_SCANS_PRESS: u8 = 5; pub const MATRIX_DEBOUNCE_SCANS_RELEASE: u8 = 5; +/// Minimum scans between two press events for the same key (50ms at 250μs scan cadence). +pub const MIN_PRESS_SPACING_SCANS: u32 = 200; + /// Initial scan iterations before trusting key state. pub const INITIAL_SCAN_PASSES: usize = 50; @@ -57,77 +63,41 @@ pub mod pins { pub const STATUS_LED: u8 = 16; } -/// Convenience macro mirroring the joystick repository for fetching typed pins. -#[macro_export] -macro_rules! get_pin { - ($pins:expr, button_row_0) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_ROW_0; - $pins.gpio0 - }}; - ($pins:expr, button_row_1) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_ROW_1; - $pins.gpio1 - }}; - ($pins:expr, button_row_2) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_ROW_2; - $pins.gpio29 - }}; - ($pins:expr, button_row_3) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_ROW_3; - $pins.gpio28 - }}; - ($pins:expr, button_col_0) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_0; - $pins.gpio12 - }}; - ($pins:expr, button_col_1) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_1; - $pins.gpio13 - }}; - ($pins:expr, button_col_2) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_2; - $pins.gpio14 - }}; - ($pins:expr, button_col_3) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_3; - $pins.gpio15 - }}; - ($pins:expr, button_col_4) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_4; - $pins.gpio26 - }}; - ($pins:expr, button_col_5) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_5; - $pins.gpio27 - }}; - ($pins:expr, button_col_6) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_6; - $pins.gpio7 - }}; - ($pins:expr, button_col_7) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_7; - $pins.gpio8 - }}; - ($pins:expr, button_col_8) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_8; - $pins.gpio6 - }}; - ($pins:expr, button_col_9) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_9; - $pins.gpio9 - }}; - ($pins:expr, button_col_10) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_10; - $pins.gpio10 - }}; - ($pins:expr, button_col_11) => {{ - const _: u8 = $crate::hardware::pins::BUTTON_COL_11; - $pins.gpio11 - }}; - ($pins:expr, status_led) => {{ - const _: u8 = $crate::hardware::pins::STATUS_LED; - $pins.gpio16 - }}; +/// Dynamic pin types for matrix scanning and status LED control. +pub type MatrixRowPin = Pin; +pub type MatrixColPin = Pin; +pub type StatusLedPin = Pin; + +/// Consume the machine GPIO pins and split out matrix and status LED pins. +pub fn split_board_pins(pins: Pins) -> ([MatrixRowPin; KEY_ROWS], [MatrixColPin; KEY_COLS], StatusLedPin) { + let rows = [ + pins.gpio0.into_pull_up_input().into_dyn_pin(), + pins.gpio1.into_pull_up_input().into_dyn_pin(), + pins.gpio29.into_pull_up_input().into_dyn_pin(), + pins.gpio28.into_pull_up_input().into_dyn_pin(), + ]; + + let cols = [ + pins.gpio12.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio13.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio14.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio15.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio26.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio27.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio7.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio8.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio6.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio9.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio10.into_push_pull_output().into_pull_type::().into_dyn_pin(), + pins.gpio11.into_push_pull_output().into_pull_type::().into_dyn_pin(), + ]; + + let status_led = pins + .gpio16 + .into_function::() + .into_pull_type::(); + + (rows, cols, status_led) } #[cfg(all(test, feature = "std"))] diff --git a/rp2040/src/lib.rs b/rp2040/src/lib.rs index 5aeaf98..86116c8 100644 --- a/rp2040/src/lib.rs +++ b/rp2040/src/lib.rs @@ -2,17 +2,21 @@ //! CMDR Keyboard 42 firmware library for RP2040. //! -//! This crate mirrors the structure of the joystick firmware to keep hardware -//! details, status handling, and keyboard processing modular. - -pub mod button_matrix; +//! This crate mirrors the structure used by earlier CMDR firmware so the +//! keyboard shares the same modular layout for hardware details, status +//! handling, and key processing. +#[cfg(not(feature = "std"))] +pub mod board; pub mod bootloader; +pub mod button_matrix; pub mod hardware; pub mod keyboard; pub mod layout; pub mod status; -pub use button_matrix::ButtonMatrix; +#[cfg(not(feature = "std"))] +pub use board::{Board, BoardParts, KeyboardMatrix, KeyboardStatusLed}; +pub use button_matrix::{ButtonMatrix, MatrixPinAccess, MatrixPins}; pub use hardware::{ KEY_COLS, KEY_ROWS, MATRIX_DEBOUNCE_SCANS_PRESS, MATRIX_DEBOUNCE_SCANS_RELEASE, NUMBER_OF_KEYS, XTAL_FREQ_HZ, pins, timers, usb, diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs index b2ca642..50627f6 100644 --- a/rp2040/src/main.rs +++ b/rp2040/src/main.rs @@ -8,25 +8,16 @@ #![no_main] use cmdr_keyboard_42::hardware::{self, timers}; -use cmdr_keyboard_42::{bootloader, ButtonMatrix, KeyboardState, StatusLed, StatusMode}; -use cortex_m::delay::Delay; -use embedded_hal::digital::{InputPin, OutputPin}; +use cmdr_keyboard_42::{Board, BoardParts, KeyboardState, StatusMode, bootloader}; use embedded_hal_0_2::timer::CountDown; use fugit::ExtU32; use panic_halt as _; -use rp2040_hal::{ - Sio, - clocks::{Clock, init_clocks_and_plls}, - gpio::Pins, - pac, - pio::PIOExt, - timer::Timer, - watchdog::Watchdog, -}; -use usb_device::class_prelude::*; -use usb_device::prelude::*; +use usb_device::UsbError; use usb_device::device::UsbDeviceState; +use usb_device::prelude::*; +use usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig; use usbd_human_interface_device::page::Keyboard; +use usbd_human_interface_device::prelude::UsbHidError; use usbd_human_interface_device::prelude::*; #[unsafe(link_section = ".boot2")] @@ -36,119 +27,20 @@ pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; #[rp2040_hal::entry] fn main() -> ! { - let mut pac = pac::Peripherals::take().unwrap(); - - let mut watchdog = Watchdog::new(pac.WATCHDOG); - - let clocks = init_clocks_and_plls( - hardware::XTAL_FREQ_HZ, - pac.XOSC, - pac.CLOCKS, - pac.PLL_SYS, - pac.PLL_USB, - &mut pac.RESETS, - &mut watchdog, - ) - .ok() - .unwrap(); - - let core = pac::CorePeripherals::take().unwrap(); - let sio = Sio::new(pac.SIO); - - let pins = Pins::new( - pac.IO_BANK0, - pac.PADS_BANK0, - sio.gpio_bank0, - &mut pac.RESETS, - ); - - let button_matrix_row_pins: &mut [&mut dyn InputPin; - hardware::KEY_ROWS] = &mut [ - &mut cmdr_keyboard_42::get_pin!(pins, button_row_0).into_pull_up_input(), - &mut cmdr_keyboard_42::get_pin!(pins, button_row_1).into_pull_up_input(), - &mut cmdr_keyboard_42::get_pin!(pins, button_row_2).into_pull_up_input(), - &mut cmdr_keyboard_42::get_pin!(pins, button_row_3).into_pull_up_input(), - ]; - - let button_matrix_col_pins: &mut [&mut dyn OutputPin; - hardware::KEY_COLS] = &mut [ - &mut cmdr_keyboard_42::get_pin!(pins, button_col_0).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_1).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_2).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_3).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_4).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_5).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_6).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_7).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_8).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_9).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_10).into_push_pull_output(), - &mut cmdr_keyboard_42::get_pin!(pins, button_col_11).into_push_pull_output(), - ]; - - let mut button_matrix: ButtonMatrix< - { hardware::KEY_ROWS }, - { hardware::KEY_COLS }, - { hardware::NUMBER_OF_KEYS }, - > = ButtonMatrix::new( - button_matrix_row_pins, - button_matrix_col_pins, - hardware::MATRIX_DEBOUNCE_SCANS_PRESS, - hardware::MATRIX_DEBOUNCE_SCANS_RELEASE, - ); - - let mut keyboard_state = KeyboardState::new(); - - let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); - let mut status_led = StatusLed::new( - cmdr_keyboard_42::get_pin!(pins, status_led).into_function(), - &mut pio, - sm0, - clocks.peripheral_clock.freq(), - ); - status_led.update(StatusMode::Error); - - let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); - let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); - - let mut usb_tick_count_down = timer.count_down(); - usb_tick_count_down.start(timers::USB_TICK_INTERVAL_US.micros()); - - let mut status_led_count_down = timer.count_down(); - status_led_count_down.start(timers::STATUS_LED_INTERVAL_MS.millis()); - let mut status_time_ms: u32 = 0; - let mut usb_initialized = false; - let mut usb_suspended = false; - let mut wake_on_input = false; - let mut last_activity_ms: u32 = 0; - - button_matrix.init_pins(); - - for _ in 0..hardware::INITIAL_SCAN_PASSES { - button_matrix.scan_matrix(&mut delay); - } - - let initial_pressed = button_matrix.buttons_pressed(); - if initial_pressed[0] { - bootloader::enter(&mut status_led); - } - - let usb_bus = UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new( - pac.USBCTRL_REGS, - pac.USBCTRL_DPRAM, - clocks.usb_clock, - true, - &mut pac.RESETS, - )); + let BoardParts { + mut button_matrix, + mut status_led, + mut delay, + timer, + usb_bus, + } = Board::new().into_parts(); let mut keyboard = UsbHidClassBuilder::new() - .add_device( - usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig::default(), - ) - .build(&usb_bus); + .add_device(NKROBootKeyboardConfig::default()) + .build(usb_bus); let mut usb_dev = - UsbDeviceBuilder::new(&usb_bus, UsbVidPid(hardware::usb::VID, hardware::usb::PID)) + UsbDeviceBuilder::new(usb_bus, UsbVidPid(hardware::usb::VID, hardware::usb::PID)) .strings(&[StringDescriptors::default() .manufacturer(hardware::usb::MANUFACTURER) .product(hardware::usb::PRODUCT) @@ -156,22 +48,45 @@ fn main() -> ! { .unwrap() .build(); + let mut keyboard_state = KeyboardState::new(); + status_led.update(StatusMode::Error); + + button_matrix.prime(&mut delay, hardware::INITIAL_SCAN_PASSES); + let initial_pressed = button_matrix.buttons_pressed(); + if initial_pressed[0] { + bootloader::enter(&mut status_led); + } + + let mut usb_tick = timer.count_down(); + usb_tick.start(timers::USB_TICK_INTERVAL_US.micros()); + + let mut status_tick = timer.count_down(); + status_tick.start(timers::STATUS_LED_INTERVAL_MS.millis()); + + let mut status_time_ms: u32 = 0; + let mut usb_initialized = false; + let mut usb_suspended = false; + let mut wake_on_input = false; + let mut last_activity_ms: u32 = 0; + loop { - if status_led_count_down.wait().is_ok() { + if status_tick.wait().is_ok() { status_time_ms = status_time_ms.saturating_add(timers::STATUS_LED_INTERVAL_MS); let idle_elapsed = status_time_ms.saturating_sub(last_activity_ms); let idle_mode = usb_initialized && idle_elapsed >= timers::IDLE_TIMEOUT_MS; let usb_active = usb_initialized && !idle_mode; status_led.apply_summary( - keyboard_state.status_summary(usb_initialized, usb_active, usb_suspended, idle_mode), + keyboard_state.status_summary( + usb_initialized, + usb_active, + usb_suspended, + idle_mode, + ), status_time_ms, ); } - // Skip high-frequency scanning when suspended to save power - // Only scan periodically to detect wake-up inputs let should_scan = if usb_suspended { - // When suspended, reduce scan frequency by factor of 20 (every ~5ms instead of 250μs) static mut SUSPENDED_SCAN_COUNTER: u8 = 0; unsafe { SUSPENDED_SCAN_COUNTER = (SUSPENDED_SCAN_COUNTER + 1) % 20; @@ -181,7 +96,7 @@ fn main() -> ! { true }; - if usb_tick_count_down.wait().is_ok() && should_scan { + if usb_tick.wait().is_ok() && should_scan { button_matrix.scan_matrix(&mut delay); let pressed_keys = button_matrix.buttons_pressed(); @@ -205,20 +120,15 @@ fn main() -> ! { bootloader::enter(&mut status_led); } - // Check for input activity if pressed_keys.iter().any(|pressed| *pressed) { last_activity_ms = status_time_ms; - - // Wake from USB suspend if input detected if wake_on_input && usb_suspended { - // TODO: Implement remote wakeup if supported by host wake_on_input = false; } } let keyboard_report = keyboard_state.process_scan(pressed_keys); - // Only transmit USB reports when not suspended if !usb_suspended { match keyboard.device().write_report(keyboard_report) { Err(UsbHidError::WouldBlock) | Err(UsbHidError::Duplicate) => {} @@ -229,7 +139,7 @@ fn main() -> ! { keyboard_state.mark_stopped(); usb_initialized = false; } - }; + } } match keyboard.tick() { @@ -238,7 +148,7 @@ fn main() -> ! { keyboard_state.mark_stopped(); usb_initialized = false; } - }; + } } if usb_dev.poll(&mut [&mut keyboard]) { @@ -257,21 +167,16 @@ fn main() -> ! { } } - // Check USB device state for suspend/resume handling let usb_state = usb_dev.state(); let was_suspended = usb_suspended; usb_suspended = usb_state == UsbDeviceState::Suspend; - // Handle USB resume transition if was_suspended && !usb_suspended { - // Device was suspended and is now resumed last_activity_ms = status_time_ms; wake_on_input = false; } - // Handle USB suspend transition if !was_suspended && usb_suspended { - // Device has just been suspended - enter power saving mode wake_on_input = true; } } diff --git a/rp2040/src/status.rs b/rp2040/src/status.rs index b1b2168..339312c 100644 --- a/rp2040/src/status.rs +++ b/rp2040/src/status.rs @@ -1,4 +1,4 @@ -//! WS2812 status LED driver adapted from the joystick firmware. +//! Minimal status LED driver for the CMDR keyboard. use rp2040_hal::{ gpio::AnyPin, @@ -13,37 +13,16 @@ const COLOR_BLUE: RGB8 = RGB8 { r: 10, g: 4, b: 10 }; const COLOR_ORANGE: RGB8 = RGB8 { r: 5, g: 10, b: 0 }; const COLOR_RED: RGB8 = RGB8 { r: 20, g: 0, b: 0 }; const COLOR_PURPLE: RGB8 = RGB8 { r: 0, g: 10, b: 10 }; +const BREATH_PERIOD_MS: u32 = 3200; -#[allow(dead_code)] -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StatusMode { - Off = 0, - Normal = 1, - NormalFlash = 2, - Activity = 3, - ActivityFlash = 4, - Idle = 5, - Other = 6, - OtherFlash = 7, - Warning = 8, - Error = 9, - Bootloader = 10, - Power = 11, - Suspended = 12, -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct SystemState { - pub usb_active: bool, - pub usb_initialized: bool, - pub usb_suspended: bool, - pub idle_mode: bool, - pub calibration_active: bool, - pub throttle_hold_enable: bool, - pub vt_enable: bool, - pub caps_lock_active: bool, - pub sticky_armed: bool, - pub sticky_latched: bool, + Off, + Active, + Idle, + Suspended, + Error, + Bootloader, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -77,195 +56,6 @@ impl StatusSummary { idle_mode, } } - - pub fn to_system_state(self) -> SystemState { - SystemState { - usb_active: self.usb_active, - usb_initialized: self.usb_initialized, - usb_suspended: self.usb_suspended, - idle_mode: self.idle_mode, - calibration_active: false, - throttle_hold_enable: false, - vt_enable: false, - caps_lock_active: self.caps_lock_active, - sticky_armed: self.sticky_armed, - sticky_latched: self.sticky_latched, - } - } -} - -#[derive(Copy, Clone)] -enum LedEffect { - Solid, - Blink { period_ms: u32 }, - Heartbeat { period_ms: u32 }, -} - -#[derive(Copy, Clone)] -struct ModeDescriptor { - color: RGB8, - effect: LedEffect, -} - -const HEARTBEAT_POWER_MS: u32 = 800; -const HEARTBEAT_IDLE_MS: u32 = 3200; - -impl LedEffect { - fn update_interval_ms(self) -> u32 { - match self { - LedEffect::Solid => 0, - LedEffect::Blink { period_ms } => period_ms / 2, - LedEffect::Heartbeat { .. } => 10, - } - } - - fn color_for(self, base: RGB8, elapsed_ms: u32) -> RGB8 { - match self { - LedEffect::Solid => base, - LedEffect::Blink { period_ms } => { - if period_ms == 0 { - return base; - } - let half = (period_ms / 2).max(1); - if elapsed_ms % period_ms < half { - base - } else { - COLOR_OFF - } - } - LedEffect::Heartbeat { period_ms } => { - let period = period_ms.max(1); - let phase = elapsed_ms % period; - let half = (period / 2).max(1); - let ramp = if phase < half { - ((phase * 255) / half) as u8 - } else { - (((period - phase) * 255) / half) as u8 - }; - scale_color(base, ramp) - } - } - } -} - -const fn descriptor_for(mode: StatusMode, base_mode: StatusMode) -> ModeDescriptor { - match mode { - StatusMode::Off => ModeDescriptor { - color: COLOR_OFF, - effect: LedEffect::Solid, - }, - StatusMode::Normal => ModeDescriptor { - color: COLOR_GREEN, - effect: LedEffect::Solid, - }, - StatusMode::NormalFlash => ModeDescriptor { - color: COLOR_GREEN, - effect: LedEffect::Blink { period_ms: 1000 }, - }, - StatusMode::Activity => ModeDescriptor { - color: COLOR_BLUE, - effect: LedEffect::Solid, - }, - StatusMode::ActivityFlash => ModeDescriptor { - color: COLOR_BLUE, - effect: LedEffect::Blink { period_ms: 600 }, - }, - StatusMode::Idle => match base_mode { - StatusMode::Activity | StatusMode::ActivityFlash => ModeDescriptor { - color: COLOR_BLUE, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_IDLE_MS, - }, - }, - StatusMode::Other | StatusMode::OtherFlash => ModeDescriptor { - color: COLOR_ORANGE, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_IDLE_MS, - }, - }, - StatusMode::NormalFlash | StatusMode::Normal => ModeDescriptor { - color: COLOR_GREEN, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_IDLE_MS, - }, - }, - StatusMode::Warning | StatusMode::Error => ModeDescriptor { - color: COLOR_RED, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_IDLE_MS, - }, - }, - _ => ModeDescriptor { - color: COLOR_GREEN, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_IDLE_MS, - }, - }, - }, - StatusMode::Other => ModeDescriptor { - color: COLOR_ORANGE, - effect: LedEffect::Solid, - }, - StatusMode::OtherFlash => ModeDescriptor { - color: COLOR_ORANGE, - effect: LedEffect::Blink { period_ms: 600 }, - }, - StatusMode::Warning => ModeDescriptor { - color: COLOR_RED, - effect: LedEffect::Blink { period_ms: 500 }, - }, - StatusMode::Error => ModeDescriptor { - color: COLOR_RED, - effect: LedEffect::Solid, - }, - StatusMode::Bootloader => ModeDescriptor { - color: COLOR_PURPLE, - effect: LedEffect::Solid, - }, - StatusMode::Power => ModeDescriptor { - color: COLOR_GREEN, - effect: LedEffect::Heartbeat { - period_ms: HEARTBEAT_POWER_MS, - }, - }, - StatusMode::Suspended => ModeDescriptor { - color: COLOR_OFF, - effect: LedEffect::Solid, - }, - } -} - -fn scale_color(base: RGB8, brightness: u8) -> RGB8 { - let scale = brightness as u16; - RGB8 { - r: ((base.r as u16 * scale) / 255) as u8, - g: ((base.g as u16 * scale) / 255) as u8, - b: ((base.b as u16 * scale) / 255) as u8, - } -} - -fn determine_base_mode(system_state: SystemState) -> StatusMode { - if system_state.usb_suspended { - StatusMode::Suspended - } else if system_state.caps_lock_active { - StatusMode::Warning - } else if system_state.sticky_latched { - StatusMode::ActivityFlash - } else if system_state.sticky_armed { - StatusMode::Activity - } else if system_state.calibration_active { - StatusMode::ActivityFlash - } else if !system_state.usb_initialized { - StatusMode::Power - } else if !system_state.usb_active { - StatusMode::NormalFlash - } else if system_state.vt_enable { - StatusMode::Activity - } else if system_state.throttle_hold_enable { - StatusMode::Other - } else { - StatusMode::Normal - } } pub struct StatusLed @@ -277,7 +67,6 @@ where ws2812_direct: Ws2812Direct, current_mode: StatusMode, mode_started_at: Option, - last_update_time: Option, } impl StatusLed @@ -292,142 +81,109 @@ where sm: UninitStateMachine<(P, SM)>, clock_freq: fugit::HertzU32, ) -> Self { - let ws2812_direct = Ws2812Direct::new(pin, pio, sm, clock_freq); - let mut status = Self { - ws2812_direct, + let mut led = Self { + ws2812_direct: Ws2812Direct::new(pin, pio, sm, clock_freq), current_mode: StatusMode::Off, mode_started_at: None, - last_update_time: None, }; - status.write_color(COLOR_OFF); - status + led.write_color(COLOR_OFF); + led } - pub fn update_from_system_state(&mut self, system_state: SystemState, current_time: u32) { - let base_mode = determine_base_mode(system_state); - let desired_mode = if system_state.idle_mode - && system_state.usb_initialized - && base_mode != StatusMode::Off - && base_mode != StatusMode::Power - { - (StatusMode::Idle, base_mode) - } else { - (base_mode, base_mode) - }; - - self.set_mode(desired_mode.0, desired_mode.1, current_time); - } - - pub fn set_mode(&mut self, mode: StatusMode, base_mode: StatusMode, current_time: u32) { - let force_update = mode != self.current_mode; + pub fn update(&mut self, mode: StatusMode) { self.current_mode = mode; - let descriptor = descriptor_for(mode, base_mode); - if force_update { - let start_time = match descriptor.effect { - LedEffect::Heartbeat { period_ms } => { - let half = (period_ms / 2).max(1); - current_time.saturating_sub(half) - } - _ => current_time, - }; - self.mode_started_at = Some(start_time); - self.last_update_time = None; - } - - self.update_display(current_time, force_update, base_mode); + self.mode_started_at = None; + let color = mode_color(mode, 0); + self.write_color(color); } - pub fn update_display(&mut self, current_time: u32, force_update: bool, base_mode: StatusMode) { - let descriptor = descriptor_for(self.current_mode, base_mode); - let interval = descriptor.effect.update_interval_ms(); - - if !force_update { - if interval == 0 { - return; - } - if let Some(last) = self.last_update_time - && current_time.saturating_sub(last) < interval - { - return; - } - } - - let elapsed = self - .mode_started_at - .map(|start| current_time.saturating_sub(start)) - .unwrap_or(0); - let color = descriptor.effect.color_for(descriptor.color, elapsed); - + pub fn apply_summary(&mut self, summary: StatusSummary, current_time_ms: u32) { + let mode = summary_to_mode(summary); + let elapsed = if self.current_mode != mode { + self.current_mode = mode; + self.mode_started_at = Some(current_time_ms); + 0 + } else { + let start = self.mode_started_at.unwrap_or(current_time_ms); + current_time_ms.saturating_sub(start) + }; + let base_color = mode_color(mode, elapsed); + let color = highlight_color(summary).unwrap_or(base_color); self.write_color(color); - self.last_update_time = Some(current_time); } fn write_color(&mut self, color: RGB8) { let _ = self.ws2812_direct.write([color].iter().copied()); } +} - pub fn apply_summary(&mut self, summary: StatusSummary, current_time: u32) { - self.update_from_system_state(summary.to_system_state(), current_time); +fn summary_to_mode(summary: StatusSummary) -> StatusMode { + if summary.usb_suspended { + StatusMode::Suspended + } else if !summary.usb_initialized { + StatusMode::Off + } else if summary.idle_mode { + StatusMode::Idle + } else if summary.usb_active { + StatusMode::Active + } else { + StatusMode::Off } } -impl StatusLed -where - I: AnyPin, - P: PIOExt, - SM: StateMachineIndex, -{ - pub fn update(&mut self, mode: StatusMode) { - self.set_mode(mode, mode, 0); +fn highlight_color(summary: StatusSummary) -> Option { + if summary.sticky_latched { + Some(COLOR_PURPLE) + } else if summary.sticky_armed { + Some(COLOR_BLUE) + } else if summary.caps_lock_active { + Some(COLOR_ORANGE) + } else { + None } } -#[cfg(all(test, feature = "std"))] -mod tests { - use super::*; - - #[test] - fn caps_lock_overrides_with_warning() { - let state = SystemState { - caps_lock_active: true, - ..SystemState::default() - }; - assert_eq!(determine_base_mode(state), StatusMode::Warning); - } - - #[test] - fn sticky_states_map_to_activity_modes() { - let state = SystemState { - sticky_latched: true, - ..SystemState::default() - }; - assert_eq!(determine_base_mode(state), StatusMode::ActivityFlash); - - let state = SystemState { - sticky_armed: true, - ..SystemState::default() - }; - assert_eq!(determine_base_mode(state), StatusMode::Activity); - } - - #[test] - fn usb_not_active_flashes_normal() { - let state = SystemState { - usb_initialized: true, - usb_active: false, - ..SystemState::default() - }; - assert_eq!(determine_base_mode(state), StatusMode::NormalFlash); - } - - #[test] - fn usb_suspend_takes_priority() { - let state = SystemState { - usb_suspended: true, - caps_lock_active: true, // Even with caps lock active - sticky_latched: true, // Even with sticky latched - ..SystemState::default() - }; - assert_eq!(determine_base_mode(state), StatusMode::Suspended); +fn mode_color(mode: StatusMode, elapsed_ms: u32) -> RGB8 { + match mode { + StatusMode::Off => COLOR_OFF, + StatusMode::Active => COLOR_GREEN, + StatusMode::Idle => breathe(COLOR_GREEN, elapsed_ms, BREATH_PERIOD_MS), + StatusMode::Suspended => blink(COLOR_BLUE, elapsed_ms, 2000), + StatusMode::Error => COLOR_RED, + StatusMode::Bootloader => COLOR_PURPLE, + } +} + +fn blink(color: RGB8, elapsed_ms: u32, period_ms: u32) -> RGB8 { + if period_ms == 0 { + return color; + } + let phase = (elapsed_ms / (period_ms / 2).max(1)) % 2; + if phase == 0 { color } else { COLOR_OFF } +} + +fn breathe(color: RGB8, elapsed_ms: u32, period_ms: u32) -> RGB8 { + if period_ms == 0 { + return color; + } + let period = period_ms.max(1); + let time_in_period = (elapsed_ms % period) as f32; + let period_f = period as f32; + let phase = time_in_period / period_f; + let brightness = if phase < 0.5 { + 1.0 - (phase * 2.0) + } else { + (phase - 0.5) * 2.0 + }; + let clamped = brightness.max(0.0).min(1.0); + let ramp = (clamped * 255.0 + 0.5) as u8; + scale_color(color, ramp) +} + +fn scale_color(color: RGB8, factor: u8) -> RGB8 { + RGB8 { + r: (u16::from(color.r) * u16::from(factor) / 255) as u8, + g: (u16::from(color.g) * u16::from(factor) / 255) as u8, + b: (u16::from(color.b) * u16::from(factor) / 255) as u8, } } diff --git a/tools/copy_uf2.py b/tools/copy_uf2.py new file mode 100755 index 0000000..2ea3e8f --- /dev/null +++ b/tools/copy_uf2.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Copy a UF2 artifact to a detected RP2040 mass-storage mount.""" + +from __future__ import annotations + +import argparse +import os +import sys +import time +from pathlib import Path +import shutil + +INFO_FILE = "INFO_UF2.TXT" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, required=True, help="Path to the UF2 file to copy") + parser.add_argument("--timeout", type=float, default=10.0, help="Seconds to wait for the mount") + parser.add_argument( + "--mount", + type=str, + default=os.environ.get("MOUNT", ""), + help="Explicit mount point (default: auto-detect)", + ) + return parser.parse_args() + + +def candidate_paths(explicit: str, user: str) -> list[Path]: + paths: list[Path] = [] + if explicit: + paths.append(Path(explicit)) + roots = [ + Path("/Volumes"), + Path("/media"), + Path(f"/media/{user}"), + Path("/run/media"), + Path(f"/run/media/{user}"), + ] + for root in roots: + if not root.exists() or not root.is_dir(): + continue + # Many systems mount the UF2 volume directly as a child of the root directory. + for child in root.iterdir(): + if child.is_dir(): + paths.append(child) + return paths + + +def choose_mount(explicit: str, user: str) -> Path | None: + candidates = candidate_paths(explicit, user) + if explicit: + # For an explicit mount we only care whether it exists. + path = Path(explicit) + return path if path.exists() and path.is_dir() else None + # Prefer candidates containing INFO_UF2.TXT, fall back to first existing directory. + info_candidates = [path for path in candidates if (path / INFO_FILE).exists()] + if info_candidates: + return info_candidates[0] + for path in candidates: + if path.exists() and path.is_dir(): + return path + return None + + +def main() -> int: + args = parse_args() + source = args.source + if not source.exists(): + print(f"UF2 source file not found: {source}", file=sys.stderr) + return 1 + + explicit_mount = args.mount.strip() + user = os.environ.get("USER", "") + deadline = time.time() + float(args.timeout) + + while time.time() <= deadline: + mount = choose_mount(explicit_mount, user) + if mount is not None: + if not mount.exists() or not mount.is_dir(): + time.sleep(1) + continue + destination = mount / source.name + try: + shutil.copy2(source, destination) + try: + if hasattr(os, "sync"): + os.sync() + except Exception: + pass + # Give the device a moment to process the copied file before we exit. + time.sleep(0.5) + except Exception as exc: # noqa: BLE001 + print(f"Failed to copy UF2 to {destination}: {exc}", file=sys.stderr) + return 1 + print(f"Copied {source} to {destination}") + return 0 + time.sleep(1) + + if explicit_mount: + print( + f"Mount point '{explicit_mount}' not found within {args.timeout} seconds", + file=sys.stderr, + ) + else: + print( + "Unable to detect RP2040 UF2 mount. Pass one via mount=/path", + file=sys.stderr, + ) + return 1 + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main())