Improved latency and major refactor
This commit is contained in:
parent
0689e52da0
commit
aa6ac7a0c4
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ rp2040_42/target
|
||||
rp2040_42/Cargo.lock
|
||||
rp2040_51/Cargo.lock
|
||||
*.FCBak
|
||||
rp2040/target
|
||||
|
||||
13
AGENTS.md
Normal file
13
AGENTS.md
Normal 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
567
install.sh
Executable 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
1645
rp2040/Cargo.lock
generated
Normal 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",
|
||||
]
|
||||
@ -44,6 +44,9 @@ smart-leds = "0.4.0"
|
||||
[lints.clippy]
|
||||
too_long_first_doc_paragraph = "allow"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 0
|
||||
@ -57,4 +60,8 @@ overflow-checks = false
|
||||
name = "cmdr-keyboard-42"
|
||||
test = false
|
||||
bench = false
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = []
|
||||
BIN
rp2040/firmware.uf2
Normal file
BIN
rp2040/firmware.uf2
Normal file
Binary file not shown.
194
rp2040/src/button_matrix.rs
Normal file
194
rp2040/src/button_matrix.rs
Normal 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
147
rp2040/src/hardware.rs
Normal 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
230
rp2040/src/keyboard.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -205,3 +205,21 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [
|
||||
Keyboard::NoEventIndicated, // 47 no button connected
|
||||
],
|
||||
];
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn layer_map_dimensions_match() {
|
||||
for layer in MAP.iter() {
|
||||
assert_eq!(layer.len(), NUMBER_OF_KEYS);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_buttons_are_unique() {
|
||||
assert_ne!(FN_BUTTONS[0], FN_BUTTONS[1]);
|
||||
assert_ne!(FN_BUTTONS[0], FN_BUTTONS[2]);
|
||||
}
|
||||
}
|
||||
40
rp2040/src/lib.rs
Normal file
40
rp2040/src/lib.rs
Normal 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
212
rp2040/src/main.rs
Normal 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
410
rp2040/src/status.rs
Normal 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
288
rp2040/uf2conv.py
Executable 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()
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user