Improved latency and major refactor

This commit is contained in:
Christoffer Martinsson 2025-09-17 22:36:47 +02:00
parent 0689e52da0
commit aa6ac7a0c4
20 changed files with 3772 additions and 646 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ rp2040_42/target
rp2040_42/Cargo.lock rp2040_42/Cargo.lock
rp2040_51/Cargo.lock rp2040_51/Cargo.lock
*.FCBak *.FCBak
rp2040/target

13
AGENTS.md Normal file
View File

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

567
install.sh Executable file
View File

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

1645
rp2040/Cargo.lock generated Normal file
View File

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

View File

@ -44,6 +44,9 @@ smart-leds = "0.4.0"
[lints.clippy] [lints.clippy]
too_long_first_doc_paragraph = "allow" too_long_first_doc_paragraph = "allow"
[lib]
path = "src/lib.rs"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
debug = 0 debug = 0
@ -57,4 +60,8 @@ overflow-checks = false
name = "cmdr-keyboard-42" name = "cmdr-keyboard-42"
test = false test = false
bench = false bench = false
path = "src/main.rs"
[features]
default = []
std = []

BIN
rp2040/firmware.uf2 Normal file

Binary file not shown.

194
rp2040/src/button_matrix.rs Normal file
View File

@ -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<Error = Infallible>; R],
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; 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<Error = Infallible>; R],
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; 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<Cell<bool>>,
}
impl MockInputPin {
fn new(state: Rc<Cell<bool>>) -> Self {
Self { state }
}
}
impl ErrorType for MockInputPin {
type Error = Infallible;
}
impl InputPin for MockInputPin {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(!self.state.get())
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.state.get())
}
}
struct MockOutputPin {
state: Rc<Cell<bool>>,
}
impl MockOutputPin {
fn new(state: Rc<Cell<bool>>) -> 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<Cell<bool>>,
Rc<Cell<bool>>,
) {
let row_state = Rc::new(Cell::new(false));
let col_state = Rc::new(Cell::new(false));
let row_pin: &'static mut dyn InputPin<Error = Infallible> =
Box::leak(Box::new(MockInputPin::new(row_state.clone())));
let col_pin: &'static mut dyn OutputPin<Error = Infallible> =
Box::leak(Box::new(MockOutputPin::new(col_state.clone())));
let rows: &'static mut [&'static mut dyn InputPin<Error = Infallible>; 1] =
Box::leak(Box::new([row_pin]));
let cols: &'static mut [&'static mut dyn OutputPin<Error = Infallible>; 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]);
}
}

147
rp2040/src/hardware.rs Normal file
View File

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

230
rp2040/src/keyboard.rs Normal file
View File

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

View File

@ -205,3 +205,21 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [
Keyboard::NoEventIndicated, // 47 no button connected 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]);
}
}

40
rp2040/src/lib.rs Normal file
View File

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

212
rp2040/src/main.rs Normal file
View File

@ -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<Error = core::convert::Infallible>;
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<Error = core::convert::Infallible>;
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;
}
}
}
}
}

410
rp2040/src/status.rs Normal file
View File

@ -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<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
ws2812_direct: Ws2812Direct<P, SM, I>,
current_mode: StatusMode,
mode_started_at: Option<u32>,
last_update_time: Option<u32>,
}
impl<P, SM, I> StatusLed<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
pub fn new(
pin: I,
pio: &mut PIO<P>,
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<P, SM, I> StatusLed<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
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);
}
}

288
rp2040/uf2conv.py Executable file
View File

@ -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("<II", buf[0:8])
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
def is_hex(filename):
with open(filename, mode='r') as file:
try:
for line in file:
line = line.strip()
if not line:
continue
if line[0] == ':':
continue
return False
return True
except:
return False
def convert_from_uf2(buf):
global appstartaddr
numblocks = len(buf) // 512
curraddr = None
outp = []
for blockno in range(numblocks):
ptr = blockno * 512
block = buf[ptr:ptr + 512]
hd = struct.unpack(b"<IIIIIIII", block[0:32])
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
print("Skipping block at " + str(ptr))
continue
if hd[2] & 1:
# NO-flash flag set; skip block
continue
datalen = hd[4]
if datalen > 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"<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
while len(chunk) < 256:
chunk += b"\x00"
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
assert len(block) == 512
outp.append(block)
return b"".join(outp)
class Block:
def __init__(self, addr):
self.addr = addr
self.bytes = bytearray(256)
def encode(self, blockno, numblocks):
global familyid
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack("<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, self.addr, 256, blockno, numblocks, familyid)
datapadding = b"\x00" * (512 - 256 - 32 - 4)
block = hd + self.bytes + datapadding + struct.pack("<I", UF2_MAGIC_END)
return block
def convert_from_hex_to_uf2(records):
global appstartaddr
appstartaddr = None
upper = 0
blocks = {}
for line in records:
if line[0] != ':':
continue
(lenstr, addrstr, typestr, data, chkstr) = (line[1:3], line[3:7], line[7:9], line[9:-2], line[-2:])
if int(chkstr, 16) != (-(sum(int(data[i:i+2], 16) for i in range(0, len(data), 2)) + int(typestr, 16) + int(addrstr, 16) + int(lenstr, 16)) & 0xff):
assert False, "Invalid hex checksum for line: " + line
tp = int(typestr, 16)
if tp == 4:
upper = int(data, 16) << 16
elif tp == 2:
upper = int(data, 16) << 4
elif tp == 1:
break
elif tp == 0:
addr = upper + int(addrstr, 16)
if appstartaddr == None:
appstartaddr = addr
i = 0
while i < len(data):
if addr in blocks:
block = blocks[addr]
else:
block = Block(addr & ~0xff)
blocks[addr & ~0xff] = block
block.bytes[addr & 0xff] = int(data[i:i+2], 16)
addr += 1
i += 2
blocks = sorted(blocks.values(), key=lambda x: x.addr)
return b"".join(block.encode(i, len(blocks)) for i, block in enumerate(blocks))
def main():
global appstartaddr, familyid
def error(msg):
print(msg)
sys.exit(1)
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
help='input file (HEX, BIN or UF2)')
parser.add_argument('-b' , '--base', dest='base', type=str,
default="0x2000",
help='set base address of application for BIN format (default: 0x2000)')
parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
parser.add_argument('-d' , '--device', dest="device_path",
help='select a device path to flash')
parser.add_argument('-l' , '--list', action='store_true',
help='list connected devices')
parser.add_argument('-c' , '--convert', action='store_true',
help='do not flash, just convert')
parser.add_argument('-D' , '--deploy', action='store_true',
help='just flash, do not convert')
parser.add_argument('-f' , '--family', dest='family', type=str,
default="0x0",
help='specify familyID - number or name (default: 0x0)')
parser.add_argument('-C' , '--carray', action='store_true',
help='convert binary file to a C array, not UF2')
args = parser.parse_args()
appstartaddr = int(args.base, 0)
if args.family.upper() in ["RP2040"]:
familyid = 0xe48bff56
else:
try:
familyid = int(args.family, 0)
except ValueError:
error("Family ID needs to be a number or one of: RP2040")
if args.list:
drives = get_drives()
if len(drives) == 0:
error("No drives found.")
for d in drives:
print(d, info_uf2(d))
return
if not args.input:
error("Need input file")
with open(args.input, mode='rb') as f:
inpbuf = f.read()
from_uf2 = is_uf2(inpbuf)
ext = os.path.splitext(args.input)[1].lower()
if from_uf2:
outbuf = convert_from_uf2(inpbuf)
elif is_hex(args.input):
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8").split('\n'))
elif ext == ".bin":
if args.carray:
outbuf = convert_to_carray(inpbuf)
else:
outbuf = convert_to_uf2(inpbuf)
else:
error("Extension %s not supported." % ext)
if args.deploy:
drives = get_drives()
if len(drives) == 0:
error("No drives to deploy.")
for d in drives:
print("Flashing %s (%s)" % (d, info_uf2(d)))
with open(d + "NEW.UF2", "wb") as f:
f.write(outbuf)
elif args.output == None:
if args.carray:
print(outbuf.decode("utf-8"))
else:
drives = get_drives()
if len(drives) == 1:
args.output = drives[0] + "NEW.UF2"
else:
if from_uf2:
args.output = "flash.bin"
else:
args.output = "flash.uf2"
if args.output:
with open(args.output, mode='wb') as f:
f.write(outbuf)
print("Wrote %d bytes to %s." % (len(outbuf), args.output))
def get_drives():
def check_errors(r):
if r.returncode != 0:
return []
return r.stdout.split('\n')
if sys.platform == "win32":
return [r + "\\" for r in check_errors(subprocess.run(
['wmic', 'logicaldisk', 'get', 'size,freespace,caption'],
capture_output=True, text=True)) if r and not r.startswith("Caption")]
elif sys.platform == "darwin":
def parse_os_x_mount_output(mount_output):
drives = []
for line in mount_output:
m = re.match(r'^/dev/disk.*? on (.*?) \([^/]*\)$', line)
if m:
drives.append(m.group(1) + "/")
return drives
return parse_os_x_mount_output(check_errors(subprocess.run(['mount'], capture_output=True, text=True)))
else:
def parse_linux_mount_output(mount_output):
drives = []
for line in mount_output:
words = line.split()
if len(words) >= 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()

View File

@ -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<Error = Infallible>; R],
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; 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<Error = Infallible>; R],
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; 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
}
}

View File

@ -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<Error = Infallible>; 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<Error = Infallible>; 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<KEY_ROWS, KEY_COLS, NUMBER_OF_KEYS> =
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<P, SM, I>(
status_led: &mut Ws2812StatusLed<P, SM, I>,
caps_lock_active: &bool,
sticky_state: &u8,
started: &bool,
) where
I: AnyPin<Function = P::PinFunction>,
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
}

View File

@ -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<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
ws2812_direct: Ws2812Direct<P, SM, I>,
state: bool,
mode: StatusMode,
}
impl<P, SM, I> Ws2812StatusLed<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
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<P>,
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;
}
}
}