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