diff --git a/.gitignore b/.gitignore index 64f4124..7145b50 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ rp2040_42/target rp2040_42/Cargo.lock rp2040_51/Cargo.lock *.FCBak +rp2040/target diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fc94147 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# Assistant Configuration + +This file contains configuration and commands for the Claude assistant working on the CMtec CMDR Joystick 25. + +## Global Rules + +- Always describe what you thinking and your plan befor starting to change files. +- Make sure code have max 5 indentation levels +- Use classes, arrays, structs, etc for clean organization +- Make sure the codebase is manageable and easily readable +- Always check code (compile/check) +- Always fix compile warnings +- Do not try to deploy project to hardware diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..febb628 --- /dev/null +++ b/install.sh @@ -0,0 +1,567 @@ +#!/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_42/.cargo/config.toml b/rp2040/.cargo/config.toml similarity index 100% rename from rp2040_42/.cargo/config.toml rename to rp2040/.cargo/config.toml diff --git a/rp2040/Cargo.lock b/rp2040/Cargo.lock new file mode 100644 index 0000000..caa2e76 --- /dev/null +++ b/rp2040/Cargo.lock @@ -0,0 +1,1645 @@ +# This file is automatically @generated by Cargo. +# 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", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "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", + "static_cell", + "usb-device", + "usbd-human-interface-device", + "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", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +dependencies = [ + "defmt", +] + +[[package]] +name = "embedded-hal-async" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874b6a17738fc273ec753618bac60ddaeac48cb1d7684c3e7bd472e57a28b817" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3529a07095650187788833d585c219761114005d5976185760cf794d265b6a5c" + +[[package]] +name = "frunk_derives" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a956ef36c377977e512e227dcad20f68c2786ac7a54dacece3746046fea5ce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "funty" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +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", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive 0.7.3", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "option-block" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f2c5d345596a14d7c8b032a68f437955f0059f2eb9a5972371c84f7eef3227" + +[[package]] +name = "packed_struct" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b29691432cc9eff8b282278473b63df73bea49bc3ec5e67f31a3ae9c3ec190" +dependencies = [ + "bitvec", + "packed_struct_codegen", +] + +[[package]] +name = "packed_struct_codegen" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd6706dfe50d53e0f6aa09e12c034c44faacd23e966ae5a209e8bdb8f179f98" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum 0.5.11", + "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" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand_core" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rp-binary-info" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "defmt", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio 0.2.1", + "rand_core", + "rp-binary-info 0.1.1", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "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", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df34e571fa9993fa6f99131a374d58ca3d694b75f9baac93458fe0d6057bf0" +dependencies = [ + "smart-leds-trait 0.3.1", +] + +[[package]] +name = "smart-leds-trait" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf6d833fa93f16a1c1874e62c2aebe8567e5bdd436d59bf543ed258b6f7a8e3" +dependencies = [ + "rgb", +] + +[[package]] +name = "smart-leds-trait" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edeb89c73244414bb0568611690dd095b2358b3fda5bae65ad784806cca00157" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_cell" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +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", + "portable-atomic", +] + +[[package]] +name = "usbd-human-interface-device" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d19fd35b28737afde3b854c0dc354df41fceada782f3add0a8115794eda2624" +dependencies = [ + "frunk", + "fugit", + "heapless 0.8.0", + "num_enum 0.7.3", + "option-block", + "packed_struct", + "usb-device", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef27fde9c7b196ae28a24ee85cd50d73d3e39a3306f971090f871e8c6e7c4d24" +dependencies = [ + "cortex-m", + "embedded-hal 0.2.7", + "fugit", + "nb 1.1.0", + "pio 0.2.1", + "rp2040-hal", + "smart-leds-trait 0.2.1", + "smart-leds-trait 0.3.1", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/rp2040_42/Cargo.toml b/rp2040/Cargo.toml similarity index 95% rename from rp2040_42/Cargo.toml rename to rp2040/Cargo.toml index f43b04c..4a2ece5 100644 --- a/rp2040_42/Cargo.toml +++ b/rp2040/Cargo.toml @@ -44,6 +44,9 @@ smart-leds = "0.4.0" [lints.clippy] too_long_first_doc_paragraph = "allow" +[lib] +path = "src/lib.rs" + [profile.release] codegen-units = 1 debug = 0 @@ -57,4 +60,8 @@ overflow-checks = false name = "cmdr-keyboard-42" test = false bench = false +path = "src/main.rs" +[features] +default = [] +std = [] diff --git a/rp2040_42/build.rs b/rp2040/build.rs similarity index 100% rename from rp2040_42/build.rs rename to rp2040/build.rs diff --git a/rp2040/firmware.uf2 b/rp2040/firmware.uf2 new file mode 100644 index 0000000..747db56 Binary files /dev/null and b/rp2040/firmware.uf2 differ diff --git a/rp2040_42/memory.x b/rp2040/memory.x similarity index 100% rename from rp2040_42/memory.x rename to rp2040/memory.x diff --git a/rp2040/src/button_matrix.rs b/rp2040/src/button_matrix.rs new file mode 100644 index 0000000..2fbacf0 --- /dev/null +++ b/rp2040/src/button_matrix.rs @@ -0,0 +1,194 @@ +//! 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. + +use core::convert::Infallible; +use cortex_m::delay::Delay; +use embedded_hal::digital::{InputPin, OutputPin}; + +/// 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], +} + +impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { + pub fn new( + rows: &'a mut [&'a mut dyn InputPin; R], + cols: &'a mut [&'a mut dyn OutputPin; C], + press_threshold: u8, + release_threshold: u8, + ) -> Self { + Self { + rows, + cols, + pressed: [false; N], + press_threshold, + release_threshold, + debounce_counter: [0; N], + } + } + + pub fn init_pins(&mut self) { + for col in self.cols.iter_mut() { + col.set_high().unwrap(); + } + } + + pub fn scan_matrix(&mut self, delay: &mut Delay) { + for col_index in 0..self.cols.len() { + self.cols[col_index].set_low().unwrap(); + delay.delay_us(1); + self.process_column(col_index); + self.cols[col_index].set_high().unwrap(); + 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(); + + if current_state == self.pressed[button_index] { + self.debounce_counter[button_index] = 0; + continue; + } + + self.debounce_counter[button_index] = + self.debounce_counter[button_index].saturating_add(1); + + let threshold = if current_state { + self.press_threshold + } else { + self.release_threshold + }; + + if self.debounce_counter[button_index] >= threshold { + self.pressed[button_index] = current_state; + self.debounce_counter[button_index] = 0; + } + } + } + + pub fn buttons_pressed(&mut self) -> [bool; N] { + self.pressed + } +} + +#[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>, + } + + impl MockInputPin { + fn new(state: Rc>) -> Self { + Self { state } + } + } + + impl ErrorType for MockInputPin { + type Error = Infallible; + } + + impl InputPin for MockInputPin { + fn is_high(&mut self) -> Result { + Ok(!self.state.get()) + } + + fn is_low(&mut self) -> Result { + Ok(self.state.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>, + 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, 2, 3); + + (matrix, row_state, col_state) + } + + #[test] + fn init_sets_columns_high() { + let (mut matrix, _row_state, col_state) = matrix_fixture(); + assert!(!col_state.get()); + matrix.init_pins(); + assert!(col_state.get()); + } + + #[test] + fn debounce_respects_threshold() { + let (mut matrix, row_state, _col_state) = matrix_fixture(); + let mut states = matrix.buttons_pressed(); + assert!(!states[0]); + + row_state.set(true); + matrix.process_column(0); + matrix.process_column(0); + states = matrix.buttons_pressed(); + assert!(states[0]); + + row_state.set(false); + matrix.process_column(0); + matrix.process_column(0); + states = matrix.buttons_pressed(); + assert!(states[0]); + matrix.process_column(0); + states = matrix.buttons_pressed(); + assert!(!states[0]); + } +} diff --git a/rp2040/src/hardware.rs b/rp2040/src/hardware.rs new file mode 100644 index 0000000..4d853a7 --- /dev/null +++ b/rp2040/src/hardware.rs @@ -0,0 +1,147 @@ +//! Hardware configuration and timing constants for the CMDR Keyboard 42. + +/// External crystal frequency (Hz). +pub const XTAL_FREQ_HZ: u32 = 12_000_000; + +/// Debounce scans required before a key state toggles. +pub const MATRIX_DEBOUNCE_SCANS_PRESS: u8 = 2; +pub const MATRIX_DEBOUNCE_SCANS_RELEASE: u8 = 3; + +/// Initial scan iterations before trusting key state. +pub const INITIAL_SCAN_PASSES: usize = 50; + +/// Button matrix geometry. +pub const KEY_ROWS: usize = 4; +pub const KEY_COLS: usize = 12; +pub const NUMBER_OF_KEYS: usize = KEY_ROWS * KEY_COLS; + +/// USB identification constants. +pub mod usb { + pub const VID: u16 = 0x1209; + pub const PID: u16 = 0x0001; + pub const MANUFACTURER: &str = "CMtec"; + pub const PRODUCT: &str = "CMDR Keyboard 42"; + pub const SERIAL_NUMBER: &str = "0001"; +} + +/// Timing cadence helpers used throughout the firmware. +pub mod timers { + pub const USB_REPORT_INTERVAL_MS: u32 = 10; + pub const USB_TICK_INTERVAL_US: u32 = 250; + pub const STATUS_LED_INTERVAL_MS: u32 = 4; + pub const IDLE_TIMEOUT_MS: u32 = 5_000; +} + +/// Physical GPIO assignments for the RP2040. +pub mod pins { + pub const BUTTON_ROW_0: u8 = 0; + pub const BUTTON_ROW_1: u8 = 1; + pub const BUTTON_ROW_2: u8 = 29; + pub const BUTTON_ROW_3: u8 = 28; + + pub const BUTTON_COL_0: u8 = 12; + pub const BUTTON_COL_1: u8 = 13; + pub const BUTTON_COL_2: u8 = 14; + pub const BUTTON_COL_3: u8 = 15; + pub const BUTTON_COL_4: u8 = 26; + pub const BUTTON_COL_5: u8 = 27; + pub const BUTTON_COL_6: u8 = 7; + pub const BUTTON_COL_7: u8 = 8; + pub const BUTTON_COL_8: u8 = 6; + pub const BUTTON_COL_9: u8 = 9; + pub const BUTTON_COL_10: u8 = 10; + pub const BUTTON_COL_11: u8 = 11; + + 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 + }}; +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + #[test] + fn number_of_keys_matches_rows_and_cols() { + assert_eq!(NUMBER_OF_KEYS, KEY_ROWS * KEY_COLS); + } + + #[test] + fn usb_metadata_is_consistent() { + assert_ne!(usb::VID, 0); + assert_ne!(usb::PID, 0); + assert!(!usb::MANUFACTURER.is_empty()); + assert!(!usb::PRODUCT.is_empty()); + } +} diff --git a/rp2040/src/keyboard.rs b/rp2040/src/keyboard.rs new file mode 100644 index 0000000..9314864 --- /dev/null +++ b/rp2040/src/keyboard.rs @@ -0,0 +1,230 @@ +//! Keyboard state management and HID report generation. + +use crate::NUMBER_OF_KEYS; +use crate::layout; +use crate::status::StatusSummary; +use usbd_human_interface_device::page::Keyboard; + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct KeyboardButton { + pub pressed: bool, + pub previous_pressed: bool, + pub fn_mode: u8, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum StickyState { + Inactive, + Armed, + Latched, +} + +impl StickyState { + pub fn is_armed(self) -> bool { + matches!(self, StickyState::Armed) + } + + pub fn is_latched(self) -> bool { + matches!(self, StickyState::Latched) + } +} + +pub struct KeyboardState { + buttons: [KeyboardButton; NUMBER_OF_KEYS], + sticky_state: StickyState, + sticky_key: Keyboard, + caps_lock_active: bool, + started: bool, +} + +impl KeyboardState { + pub fn new() -> Self { + Self { + buttons: [KeyboardButton::default(); NUMBER_OF_KEYS], + sticky_state: StickyState::Inactive, + sticky_key: Keyboard::NoEventIndicated, + caps_lock_active: false, + started: false, + } + } + + pub fn process_scan( + &mut self, + pressed_keys: [bool; NUMBER_OF_KEYS], + ) -> [Keyboard; NUMBER_OF_KEYS] { + let fn_mode = Self::fn_mode(&pressed_keys); + + for (index, pressed) in pressed_keys.iter().enumerate() { + self.buttons[index].pressed = *pressed; + } + + self.build_report(fn_mode) + } + + pub fn update_caps_lock(&mut self, active: bool) { + self.caps_lock_active = active; + } + + pub fn caps_lock_active(&self) -> bool { + self.caps_lock_active + } + + pub fn mark_started(&mut self) { + self.started = true; + } + + pub fn mark_stopped(&mut self) { + self.started = false; + } + + pub fn started(&self) -> bool { + self.started + } + + pub fn sticky_state(&self) -> StickyState { + self.sticky_state + } + + pub fn status_summary( + &self, + usb_initialized: bool, + usb_active: bool, + idle_mode: bool, + ) -> StatusSummary { + StatusSummary::new( + self.caps_lock_active, + matches!(self.sticky_state, StickyState::Armed), + matches!(self.sticky_state, StickyState::Latched), + usb_initialized, + usb_active, + idle_mode, + ) + } + + fn build_report(&mut self, fn_mode: u8) -> [Keyboard; NUMBER_OF_KEYS] { + let mut report = [Keyboard::NoEventIndicated; NUMBER_OF_KEYS]; + + for (index, button) in self.buttons.iter_mut().enumerate() { + let changed = button.pressed != button.previous_pressed; + let just_pressed = changed && button.pressed; + + if just_pressed + && index as u8 == layout::STICKY_BUTTON[0] + && fn_mode == layout::STICKY_BUTTON[1] + { + self.sticky_state = match self.sticky_state { + StickyState::Inactive => StickyState::Armed, + StickyState::Armed | StickyState::Latched => { + self.sticky_key = Keyboard::NoEventIndicated; + StickyState::Inactive + } + }; + } else if just_pressed + && index as u8 == layout::OS_LOCK_BUTTON[0] + && fn_mode == layout::OS_LOCK_BUTTON[1] + { + report[36] = layout::OS_LOCK_BUTTON_KEYS[0]; + report[37] = layout::OS_LOCK_BUTTON_KEYS[1]; + } + + if just_pressed { + button.fn_mode = fn_mode; + } + + let layer_key = layout::MAP[button.fn_mode as usize][index]; + if layer_key == Keyboard::NoEventIndicated { + button.previous_pressed = button.pressed; + continue; + } + + if self.sticky_state == StickyState::Armed && button.pressed { + self.sticky_key = layer_key; + self.sticky_state = StickyState::Latched; + } + + if button.pressed { + report[index] = layer_key; + } + + button.previous_pressed = button.pressed; + } + + const STICKY_REPORT_INDEX: usize = 46; + report[STICKY_REPORT_INDEX] = self.sticky_key; + + report + } + + fn fn_mode(pressed_keys: &[bool; NUMBER_OF_KEYS]) -> u8 { + let active_fn_keys = layout::FN_BUTTONS + .iter() + .filter(|key_index| pressed_keys[**key_index as usize]) + .count() as u8; + + active_fn_keys.min(2) + } +} + +impl Default for KeyboardState { + fn default() -> Self { + Self::new() + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + #[test] + fn fn_mode_caps_at_two() { + let mut pressed = [false; NUMBER_OF_KEYS]; + pressed[layout::FN_BUTTONS[0] as usize] = true; + pressed[layout::FN_BUTTONS[1] as usize] = true; + pressed[layout::FN_BUTTONS[2] as usize] = true; + + assert_eq!(KeyboardState::fn_mode(&pressed), 2); + } + + #[test] + fn sticky_button_transitions_between_states() { + let mut state = KeyboardState::new(); + + let mut pressed = [false; NUMBER_OF_KEYS]; + pressed[layout::FN_BUTTONS[0] as usize] = true; + pressed[layout::FN_BUTTONS[1] as usize] = true; + pressed[layout::STICKY_BUTTON[0] as usize] = true; + + state.process_scan(pressed); + assert_eq!(state.sticky_state(), StickyState::Armed); + + // Press another key to latch sticky + let mut pressed = [false; NUMBER_OF_KEYS]; + pressed[layout::FN_BUTTONS[0] as usize] = true; + pressed[layout::FN_BUTTONS[1] as usize] = true; + pressed[0] = true; + state.process_scan(pressed); + assert_eq!(state.sticky_state(), StickyState::Latched); + + // Press sticky again to clear + let mut pressed = [false; NUMBER_OF_KEYS]; + pressed[layout::FN_BUTTONS[0] as usize] = true; + pressed[layout::FN_BUTTONS[1] as usize] = true; + pressed[layout::STICKY_BUTTON[0] as usize] = true; + state.process_scan(pressed); + assert_eq!(state.sticky_state(), StickyState::Inactive); + } + + #[test] + fn status_summary_reflects_keyboard_state() { + let mut state = KeyboardState::new(); + state.update_caps_lock(true); + state.mark_started(); + + let summary = state.status_summary(true, true, false); + assert!(summary.caps_lock_active); + assert!(summary.usb_active); + assert!(summary.usb_initialized); + assert!(!summary.sticky_armed); + assert!(!summary.sticky_latched); + } +} diff --git a/rp2040_42/src/layout.rs b/rp2040/src/layout.rs similarity index 95% rename from rp2040_42/src/layout.rs rename to rp2040/src/layout.rs index 7698af7..eee42a9 100644 --- a/rp2040_42/src/layout.rs +++ b/rp2040/src/layout.rs @@ -205,3 +205,21 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::NoEventIndicated, // 47 no button connected ], ]; + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + #[test] + fn layer_map_dimensions_match() { + for layer in MAP.iter() { + assert_eq!(layer.len(), NUMBER_OF_KEYS); + } + } + + #[test] + fn fn_buttons_are_unique() { + assert_ne!(FN_BUTTONS[0], FN_BUTTONS[1]); + assert_ne!(FN_BUTTONS[0], FN_BUTTONS[2]); + } +} diff --git a/rp2040/src/lib.rs b/rp2040/src/lib.rs new file mode 100644 index 0000000..1a66698 --- /dev/null +++ b/rp2040/src/lib.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! 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; +pub mod hardware; +pub mod keyboard; +pub mod layout; +pub mod status; + +pub use button_matrix::ButtonMatrix; +pub use hardware::{ + KEY_COLS, KEY_ROWS, MATRIX_DEBOUNCE_SCANS_PRESS, MATRIX_DEBOUNCE_SCANS_RELEASE, NUMBER_OF_KEYS, + XTAL_FREQ_HZ, pins, timers, usb, +}; +pub use keyboard::{KeyboardState, StickyState}; +pub use status::{StatusLed, StatusMode, StatusSummary}; + +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +static mut __bi_entries_start: u8 = 0; + +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +static mut __bi_entries_end: u8 = 0; + +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +static mut __sidata: u8 = 0; + +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +static mut __sdata: u8 = 0; + +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +static mut __edata: u8 = 0; diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs new file mode 100644 index 0000000..6804107 --- /dev/null +++ b/rp2040/src/main.rs @@ -0,0 +1,212 @@ +//! Project: CMtec CMDR Keyboard 42 +//! Date: 2025-03-09 +//! Author: Christoffer Martinsson +//! Email: cm@cmtec.se +//! License: Please refer to LICENSE in root directory + +#![no_std] +#![no_main] + +use cmdr_keyboard_42::hardware::{self, timers}; +use cmdr_keyboard_42::{ButtonMatrix, KeyboardState, StatusLed, StatusMode}; +use cortex_m::delay::Delay; +use embedded_hal::digital::{InputPin, OutputPin}; +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 usbd_human_interface_device::prelude::*; + +#[unsafe(link_section = ".boot2")] +#[unsafe(no_mangle)] +#[used] +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 last_activity_ms: u32 = 0; + + button_matrix.init_pins(); + + for _ in 0..hardware::INITIAL_SCAN_PASSES { + button_matrix.scan_matrix(&mut delay); + } + + if button_matrix.buttons_pressed()[0] { + status_led.update(StatusMode::Bootloader); + let gpio_activity_pin_mask: u32 = 0; + let disable_interface_mask: u32 = 0; + rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask); + } + + let usb_bus = UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + + let mut keyboard = UsbHidClassBuilder::new() + .add_device( + usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig::default(), + ) + .build(&usb_bus); + + let mut usb_dev = + UsbDeviceBuilder::new(&usb_bus, UsbVidPid(hardware::usb::VID, hardware::usb::PID)) + .strings(&[StringDescriptors::default() + .manufacturer(hardware::usb::MANUFACTURER) + .product(hardware::usb::PRODUCT) + .serial_number(hardware::usb::SERIAL_NUMBER)]) + .unwrap() + .build(); + + loop { + if status_led_count_down.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, idle_mode), + status_time_ms, + ); + } + + if usb_tick_count_down.wait().is_ok() { + button_matrix.scan_matrix(&mut delay); + let pressed_keys = button_matrix.buttons_pressed(); + if pressed_keys.iter().any(|pressed| *pressed) { + last_activity_ms = status_time_ms; + } + let keyboard_report = keyboard_state.process_scan(pressed_keys); + match keyboard.device().write_report(keyboard_report) { + Err(UsbHidError::WouldBlock) | Err(UsbHidError::Duplicate) => {} + Ok(_) => { + usb_initialized = true; + } + Err(_) => { + keyboard_state.mark_stopped(); + usb_initialized = false; + } + }; + + match keyboard.tick() { + Err(UsbHidError::WouldBlock) | Ok(_) => {} + Err(_) => { + keyboard_state.mark_stopped(); + usb_initialized = false; + } + }; + } + + if usb_dev.poll(&mut [&mut keyboard]) { + match keyboard.device().read_report() { + Err(UsbError::WouldBlock) => {} + Err(_) => { + keyboard_state.mark_stopped(); + usb_initialized = false; + } + Ok(leds) => { + keyboard_state.update_caps_lock(leds.caps_lock); + keyboard_state.mark_started(); + usb_initialized = true; + last_activity_ms = status_time_ms; + } + } + } + } +} diff --git a/rp2040/src/status.rs b/rp2040/src/status.rs new file mode 100644 index 0000000..348f512 --- /dev/null +++ b/rp2040/src/status.rs @@ -0,0 +1,410 @@ +//! WS2812 status LED driver adapted from the joystick firmware. + +use rp2040_hal::{ + gpio::AnyPin, + pio::{PIO, PIOExt, StateMachineIndex, UninitStateMachine}, +}; +use smart_leds::{RGB8, SmartLedsWrite}; +use ws2812_pio::Ws2812Direct; + +const COLOR_OFF: RGB8 = RGB8 { r: 0, g: 0, b: 0 }; +const COLOR_GREEN: RGB8 = RGB8 { r: 10, g: 7, b: 0 }; +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 }; + +#[allow(dead_code)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +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, +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct SystemState { + pub usb_active: bool, + pub usb_initialized: 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, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StatusSummary { + pub caps_lock_active: bool, + pub sticky_armed: bool, + pub sticky_latched: bool, + pub usb_initialized: bool, + pub usb_active: bool, + pub idle_mode: bool, +} + +impl StatusSummary { + pub const fn new( + caps_lock_active: bool, + sticky_armed: bool, + sticky_latched: bool, + usb_initialized: bool, + usb_active: bool, + idle_mode: bool, + ) -> Self { + Self { + caps_lock_active, + sticky_armed, + sticky_latched, + usb_initialized, + usb_active, + idle_mode, + } + } + + pub fn to_system_state(self) -> SystemState { + SystemState { + usb_active: self.usb_active, + usb_initialized: self.usb_initialized, + 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 { .. } => 4, + } + } + + 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, + }, + }, + } +} + +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.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 +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + ws2812_direct: Ws2812Direct, + current_mode: StatusMode, + mode_started_at: Option, + last_update_time: Option, +} + +impl StatusLed +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + pub fn new( + pin: I, + pio: &mut PIO

, + 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, + current_mode: StatusMode::Off, + mode_started_at: None, + last_update_time: None, + }; + status.write_color(COLOR_OFF); + status + } + + 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; + 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); + } + + 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); + + 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); + } +} + +impl StatusLed +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + pub fn update(&mut self, mode: StatusMode) { + self.set_mode(mode, mode, 0); + } +} + +#[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); + } +} diff --git a/rp2040/uf2conv.py b/rp2040/uf2conv.py new file mode 100755 index 0000000..1fcc17f --- /dev/null +++ b/rp2040/uf2conv.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 + +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + str(ptr) + newaddr = hd[3] + if curraddr == None: + appstartaddr = newaddr + curraddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + str(ptr) + if padding > 10*1024*1024: + assert False, "More than 10M of padding needed at " + str(ptr) + if padding % 4 != 0: + assert False, "Non-word padding size at " + str(ptr) + while padding > 0: + padding -= 4 + outp.append(b"\x00\x00\x00\x00") + outp.append(block[32:32 + datalen]) + curraddr = newaddr + datalen + return b"".join(outp) + +def convert_to_carray(file_content): + outp = "const unsigned char bindata_len = %d;\n" % len(file_content) + outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr:ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(b"= 3: + drives.append(words[2] + "/") + return drives + return parse_linux_mount_output(check_errors(subprocess.run(['mount'], capture_output=True, text=True))) + +def info_uf2(d): + try: + with open(d + INFO_FILE, mode='r') as f: + return f.read() + except: + return "UF2 Bootloader" + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rp2040_42/src/button_matrix.rs b/rp2040_42/src/button_matrix.rs deleted file mode 100644 index 8803d46..0000000 --- a/rp2040_42/src/button_matrix.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Project: CMtec CMDR Keyboard 42 -//! Date: 2025-03-09 -//! Author: Christoffer Martinsson -//! Email: cm@cmtec.se -//! License: Please refer to LICENSE in root directory - -use core::convert::Infallible; -use cortex_m::delay::Delay; -use embedded_hal::digital::{InputPin, OutputPin}; - -/// Button matrix driver -/// -/// # Example -/// ``` -/// let button_matrix: ButtonMatrix<4, 6, 48> = ButtonMatrix::new(row_pins, col_pins, 5); -/// ``` -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], - debounce: u8, - debounce_counter: [u8; N], -} - -impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { - /// Creates a new button matrix. - /// - /// # Arguments - /// - /// * `rows` - An array of references to the row pins. - /// * `cols` - An array of references to the column pins. - /// * `debounce` - The debounce time in number of scans. - pub fn new( - rows: &'a mut [&'a mut dyn InputPin; R], - cols: &'a mut [&'a mut dyn OutputPin; C], - debounce: u8, - ) -> Self { - Self { - rows, - cols, - pressed: [false; N], - debounce, - debounce_counter: [0; N], - } - } - - /// Initializes the button matrix. - /// This should be called once before scanning the matrix. - pub fn init_pins(&mut self) { - for col in self.cols.iter_mut() { - col.set_high().unwrap(); - } - } - - /// Scans the button matrix and updates the pressed state of each button. - /// This should be called at regular intervals. - /// Allow at least 5 times the delay compared to the needed button latency. - /// - /// # Arguments - /// - /// * `delay` - A mutable reference to a delay object. - pub fn scan_matrix(&mut self, delay: &mut Delay) { - for col_index in 0..self.cols.len() { - self.cols[col_index].set_low().unwrap(); - delay.delay_us(5); - self.process_column(col_index); - self.cols[col_index].set_high().unwrap(); - delay.delay_us(5); - } - } - - /// Processes a column of the button matrix. - /// - /// # Arguments - /// - /// * `col_index` - The index of the column to process. - 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(); - - if current_state == self.pressed[button_index] { - self.debounce_counter[button_index] = 0; - continue; - } - - self.debounce_counter[button_index] += 1; - if self.debounce_counter[button_index] >= self.debounce { - self.pressed[button_index] = current_state; - } - } - } - - /// Returns an array of booleans indicating whether each button is pressed. - pub fn buttons_pressed(&mut self) -> [bool; N] { - self.pressed - } -} diff --git a/rp2040_42/src/main.rs b/rp2040_42/src/main.rs deleted file mode 100644 index b557c77..0000000 --- a/rp2040_42/src/main.rs +++ /dev/null @@ -1,383 +0,0 @@ -//! Project: CMtec CMDR Keyboard 42 -//! Date: 2025-03-09 -//! Author: Christoffer Martinsson -//! Email: cm@cmtec.se -//! License: Please refer to LICENSE in root directory - -#![no_std] -#![no_main] - -mod button_matrix; -mod layout; -mod status_led; - -use button_matrix::ButtonMatrix; -use core::convert::Infallible; -use cortex_m::delay::Delay; -use embedded_hal::digital::{InputPin, OutputPin}; -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::{AnyPin, Pins}, - pac, - pio::{PIOExt, StateMachineIndex}, - timer::Timer, - watchdog::Watchdog, -}; -use status_led::{StatusMode, Ws2812StatusLed}; -use usb_device::class_prelude::*; -use usb_device::prelude::*; -use usbd_human_interface_device::page::Keyboard; -use usbd_human_interface_device::prelude::*; - -// The linker will place this boot block at the start of our program image. We -/// need this to help the ROM bootloader get our code up and running. -#[unsafe(link_section = ".boot2")] -#[unsafe(no_mangle)] -#[used] -pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; - -const XTAL_FREQ_HZ: u32 = 12_000_000u32; - -// Public constants -pub const KEY_ROWS: usize = 4; -pub const KEY_COLS: usize = 12; -pub const NUMBER_OF_KEYS: usize = KEY_ROWS * KEY_COLS; - -// Public types -#[derive(Copy, Clone, Default)] -pub struct KeyboardButton { - pub pressed: bool, - pub previous_pressed: bool, - pub fn_mode: u8, -} - -#[rp2040_hal::entry] -fn main() -> ! { - // Grab our singleton objects - let mut pac = pac::Peripherals::take().unwrap(); - - // Set up the watchdog driver - needed by the clock setup code - let mut watchdog = Watchdog::new(pac.WATCHDOG); - - // Configure clocks and PLLs - let clocks = init_clocks_and_plls( - 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(); - - // The single-cycle I/O block controls our GPIO pins - let sio = Sio::new(pac.SIO); - - // Set the pins to their default state - let pins = Pins::new( - pac.IO_BANK0, - pac.PADS_BANK0, - sio.gpio_bank0, - &mut pac.RESETS, - ); - - // Setting up array with pins connected to button rows - let button_matrix_row_pins: &mut [&mut dyn InputPin; KEY_ROWS] = &mut [ - &mut pins.gpio0.into_pull_up_input(), - &mut pins.gpio1.into_pull_up_input(), - &mut pins.gpio29.into_pull_up_input(), - &mut pins.gpio28.into_pull_up_input(), - ]; - - // Setting up array with pins connected to button columns - let button_matrix_col_pins: &mut [&mut dyn OutputPin; KEY_COLS] = &mut [ - &mut pins.gpio12.into_push_pull_output(), - &mut pins.gpio13.into_push_pull_output(), - &mut pins.gpio14.into_push_pull_output(), - &mut pins.gpio15.into_push_pull_output(), - &mut pins.gpio26.into_push_pull_output(), - &mut pins.gpio27.into_push_pull_output(), - &mut pins.gpio7.into_push_pull_output(), - &mut pins.gpio8.into_push_pull_output(), - &mut pins.gpio6.into_push_pull_output(), - &mut pins.gpio9.into_push_pull_output(), - &mut pins.gpio10.into_push_pull_output(), - &mut pins.gpio11.into_push_pull_output(), - ]; - - // Create button matrix object that scans all the PCB buttons - let mut button_matrix: ButtonMatrix = - ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, 5); - - // Create status LED - let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); - let mut status_led = Ws2812StatusLed::new( - pins.gpio16.into_function(), - &mut pio, - sm0, - clocks.peripheral_clock.freq(), - ); - - // Set red color to statusled indicating error if not reaching assumed state (USB connect) - status_led.update(StatusMode::Error); - - // Create keyboard button array - let mut buttons: [KeyboardButton; NUMBER_OF_KEYS] = [KeyboardButton::default(); NUMBER_OF_KEYS]; - - // Create timers/delays - 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_hid_report_count_down = timer.count_down(); - usb_hid_report_count_down.start(10.millis()); - - let mut usb_tick_count_down = timer.count_down(); - usb_tick_count_down.start(1.millis()); - - let mut status_led_count_down = timer.count_down(); - status_led_count_down.start(250.millis()); - - // Create variables to track caps lock and fn mode - let mut caps_lock_active: bool = false; - let mut fn_mode: u8; - let mut sticky_state: u8 = 0; - let mut sticky_key: Keyboard = Keyboard::NoEventIndicated; - let mut started: bool = false; - - // Initialize button matrix - button_matrix.init_pins(); - - // Scan matrix to get initial state - for _ in 0..50 { - button_matrix.scan_matrix(&mut delay); - } - - // Check if esc key is pressed while power on. If yes then enter bootloader - if button_matrix.buttons_pressed()[0] { - status_led.update(StatusMode::Bootloader); - let gpio_activity_pin_mask: u32 = 0; - let disable_interface_mask: u32 = 0; - rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask); - } - - // Configure USB - let usb_bus = UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new( - pac.USBCTRL_REGS, - pac.USBCTRL_DPRAM, - clocks.usb_clock, - true, - &mut pac.RESETS, - )); - - let mut keyboard = UsbHidClassBuilder::new() - .add_device( - usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig::default(), - ) - .build(&usb_bus); - - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0001)) - .strings(&[StringDescriptors::default() - .manufacturer("CMtec") - .product("CMDR Keyboard 42") - .serial_number("0001")]) - .unwrap() - .build(); - - loop { - if status_led_count_down.wait().is_ok() { - update_status_led(&mut status_led, &caps_lock_active, &sticky_state, &started); - } - - if usb_hid_report_count_down.wait().is_ok() { - let pressed_keys = button_matrix.buttons_pressed(); - - fn_mode = get_fn_mode(pressed_keys); - - for (index, key) in pressed_keys.iter().enumerate() { - buttons[index].pressed = *key; - } - - let keyboard_report = - get_keyboard_report(&mut buttons, fn_mode, &mut sticky_state, &mut sticky_key); - - match keyboard.device().write_report(keyboard_report) { - Err(UsbHidError::WouldBlock) => {} - Err(UsbHidError::Duplicate) => {} - Ok(_) => {} - Err(_) => { - started = false; - } - }; - } - - if usb_tick_count_down.wait().is_ok() { - button_matrix.scan_matrix(&mut delay); - - match keyboard.tick() { - Err(UsbHidError::WouldBlock) => {} - Ok(_) => {} - Err(_) => { - started = false; - } - }; - } - - if usb_dev.poll(&mut [&mut keyboard]) { - match keyboard.device().read_report() { - Err(UsbError::WouldBlock) => {} - Err(_) => { - started = false; - } - Ok(leds) => { - caps_lock_active = leds.caps_lock; - started = true; - } - } - } - } -} - -/// Update status LED colour based on function layer and capslock -/// -/// Normal = Off (OFF) -/// STICKY lock = blue/falshing blue (ACTIVITY) -/// Capslock active = flashing red (WARNING) -/// Error = steady red (ERROR) -/// -/// # Arguments -/// * `status_led` - Reference to status LED -/// * `caps_lock_active` - Is capslock active -fn update_status_led( - status_led: &mut Ws2812StatusLed, - caps_lock_active: &bool, - sticky_state: &u8, - started: &bool, -) where - I: AnyPin, - P: PIOExt, - SM: StateMachineIndex, -{ - if *caps_lock_active { - status_led.update(StatusMode::Warning); - } else if *sticky_state == 1 { - status_led.update(StatusMode::Activity); - } else if *sticky_state == 2 { - status_led.update(StatusMode::ActivityFlash); - } else if !(*started) { - status_led.update(StatusMode::NormalFlash); - } else { - status_led.update(StatusMode::Normal); - } -} - -/// Get current Fn mode (0, 1 or 2) -/// layout::FN_BUTTONS contains the keycodes for each Fn key -/// -/// # Arguments -/// -/// * `pressed_keys` - Array of pressed keys -fn get_fn_mode(pressed_keys: [bool; NUMBER_OF_KEYS]) -> u8 { - // Check how many Fn keys are pressed - let mut active_fn_keys = layout::FN_BUTTONS - .iter() - .filter(|button_id| pressed_keys[**button_id as usize]) - .count() as u8; - - // Limit Fn mode to 2 - if active_fn_keys > 2 { - active_fn_keys = 2; - } - active_fn_keys -} - -/// Generate keyboard report based on pressed keys and Fn mode (0, 1 or 2) -/// layout::MAP contains the keycodes for each key in each Fn mode -/// -/// # Arguments -/// -/// * `matrix_keys` - Array of pressed keys -/// * `fn_mode` - Current function layer -/// * `sticky_state` - Is STICKY lock active -/// * `sticky_key` - the key pressed after STICKY lock was activated -fn get_keyboard_report( - matrix_keys: &mut [KeyboardButton; NUMBER_OF_KEYS], - fn_mode: u8, - sticky_state: &mut u8, - sticky_key: &mut Keyboard, -) -> [Keyboard; NUMBER_OF_KEYS] { - let mut keyboard_report: [Keyboard; NUMBER_OF_KEYS] = - [Keyboard::NoEventIndicated; NUMBER_OF_KEYS]; - - // Filter report based on Fn mode and pressed keys - for (index, key) in matrix_keys.iter_mut().enumerate() { - // Check if STICKY button is pressed (SET STICKY) - if key.pressed != key.previous_pressed - && key.pressed - && index as u8 == layout::STICKY_BUTTON[0] - && fn_mode == layout::STICKY_BUTTON[1] - && *sticky_state == 0 - { - *sticky_state = 1; - } - // Check if STICKY button is pressed (CLEAR STICKY) - else if key.pressed != key.previous_pressed - && key.pressed - && index as u8 == layout::STICKY_BUTTON[0] - && fn_mode == layout::STICKY_BUTTON[1] - && *sticky_state != 0 - { - *sticky_state = 0; - *sticky_key = Keyboard::NoEventIndicated; - } - - // Check if OS Lock button is pressed (SET STICKY) - if key.pressed != key.previous_pressed - && key.pressed - && index as u8 == layout::OS_LOCK_BUTTON[0] - && fn_mode == layout::OS_LOCK_BUTTON[1] - { - // Index 36, 37, 38, 45, 46, 47 are not used by any other keys - keyboard_report[36] = layout::OS_LOCK_BUTTON_KEYS[0]; - keyboard_report[37] = layout::OS_LOCK_BUTTON_KEYS[1]; - } - - // Set fn mode for the pressed button - if key.pressed != key.previous_pressed && key.pressed { - key.fn_mode = fn_mode; - } - key.previous_pressed = key.pressed; - - // Skip key if defined as NoEventIndicated - if layout::MAP[key.fn_mode as usize][index] == Keyboard::NoEventIndicated { - continue; - } - - // If STICKY lock is active, hold index key pressed until STICKY lock key is pressed - // again - if *sticky_state == 1 && key.pressed { - *sticky_key = layout::MAP[key.fn_mode as usize][index]; - *sticky_state = 2; - } - - // Add defined HID key to the report - if key.pressed { - keyboard_report[index] = layout::MAP[key.fn_mode as usize][index]; - } - } - - /// Index of STICKY key in keyboard report - /// Index 36, 37, 38, 45, 46, 47 are not used by any other keys - const STICKY_REPORT_INDEX: usize = 46; - // Add sticky key to the report - keyboard_report[STICKY_REPORT_INDEX] = *sticky_key; - - keyboard_report -} diff --git a/rp2040_42/src/status_led.rs b/rp2040_42/src/status_led.rs deleted file mode 100644 index e910990..0000000 --- a/rp2040_42/src/status_led.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Project: CMtec CMDR Keyboard 42 -//! Date: 2025-03-09 -//! Author: Christoffer Martinsson -//! Email: cm@cmtec.se -//! License: Please refer to LICENSE in root directory - -use rp2040_hal::{ - gpio::AnyPin, - pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO}, -}; -use smart_leds::{SmartLedsWrite, RGB8}; -use ws2812_pio::Ws2812Direct; - -/// Status LED modes -/// -/// * OFF = Syatem offline -/// * NORMAL = All system Ok -/// * ACTIVITY = System activity -/// * OTHER = Other activity -/// * WARNING = Warning -/// * ERROR = Error -/// * BOOTLOADER = Bootloader active -#[allow(dead_code)] -#[derive(PartialEq, Eq, Copy, Clone)] -pub enum StatusMode { - Off = 0, - Normal = 1, - NormalFlash = 2, - Activity = 3, - ActivityFlash = 4, - Other = 5, - OtherFlash = 6, - Warning = 7, - Error = 8, - Bootloader = 9, -} -#[warn(dead_code)] -/// This driver uses the PIO state machine to drive a WS2812 LED -/// -/// # Example -/// -/// ``` -/// let mut status_led = Ws2812StatusLed::new( -/// pins.neopixel.into_mode(), -/// &mut pio, -/// sm0, -/// clocks.peripheral_clock.freq(), -/// ); -/// ``` -pub struct Ws2812StatusLed -where - I: AnyPin, - P: PIOExt, - SM: StateMachineIndex, -{ - ws2812_direct: Ws2812Direct, - state: bool, - mode: StatusMode, -} - -impl Ws2812StatusLed -where - I: AnyPin, - P: PIOExt, - SM: StateMachineIndex, -{ - /// Creates a new instance of this driver. - /// - /// # Arguments - /// - /// * `pin` - PIO pin - /// * `pio` - PIO instance - /// * `sm` - PIO state machine - /// * `clock_freq` - PIO clock frequency - pub fn new( - pin: I, - pio: &mut PIO

, - sm: UninitStateMachine<(P, SM)>, - clock_freq: fugit::HertzU32, - ) -> Self { - // prepare the PIO program - let ws2812_direct = Ws2812Direct::new(pin, pio, sm, clock_freq); - let state = false; - let mode = StatusMode::Off; - Self { - ws2812_direct, - state, - mode, - } - } - - /// Get current status mode - #[allow(dead_code)] - pub fn get_mode(&self) -> StatusMode { - self.mode - } - #[warn(dead_code)] - /// Update status LED - /// Depending on the mode, the LED will be set to a different colour - /// - /// * OFF = off - /// * NORMAL = green - /// * NORMALFLASH = green (flashing) - /// * ACTIVITY = blue - /// * ACTIVITYFLASH = blue (flashing) - /// * OTHER = orange - /// * OTHERFLASH = orange (flashing) - /// * WARNING = red (flashing) - /// * ERROR = red - /// * BOOTLOADER = purple - /// - /// Make sure to call this function regularly to keep the LED flashing - pub fn update(&mut self, mode: StatusMode) { - let colors: [RGB8; 10] = [ - (0, 0, 0).into(), // Off - (10, 7, 0).into(), // Green - (10, 7, 0).into(), // Green - (10, 4, 10).into(), // Blue - (10, 4, 10).into(), // Blue - (5, 10, 0).into(), // Orange - (5, 10, 0).into(), // Orange - (2, 20, 0).into(), // Red - (2, 20, 0).into(), // Red - (0, 10, 10).into(), // Purple - ]; - - if mode == StatusMode::Warning - || mode == StatusMode::NormalFlash - || mode == StatusMode::ActivityFlash - || mode == StatusMode::OtherFlash - || mode != self.mode - { - self.mode = mode; - } else { - return; - } - - if (mode == StatusMode::Warning - || mode == StatusMode::NormalFlash - || mode == StatusMode::ActivityFlash - || mode == StatusMode::OtherFlash) - && !self.state - { - self.ws2812_direct - .write([colors[mode as usize]].iter().copied()) - .unwrap(); - self.state = true; - } else if mode == StatusMode::Warning - || mode == StatusMode::NormalFlash - || mode == StatusMode::ActivityFlash - || mode == StatusMode::OtherFlash - || mode == StatusMode::Off - { - self.ws2812_direct - .write([colors[0]].iter().copied()) - .unwrap(); - self.state = false; - } else { - self.ws2812_direct - .write([colors[mode as usize]].iter().copied()) - .unwrap(); - self.state = true; - } - } -}