Compare commits
10 Commits
a629a3e94d
...
43ae84806c
| Author | SHA1 | Date | |
|---|---|---|---|
| 43ae84806c | |||
| 99a8586ba6 | |||
| 3216d007d0 | |||
| ed8f0c4aea | |||
| d937f4b256 | |||
| 1799f765dc | |||
| c5204b172b | |||
| ce714ad71d | |||
| adc69a7f40 | |||
| 2de28f38b9 |
17
AGENTS.md
17
AGENTS.md
@ -1,14 +1,23 @@
|
|||||||
# Assistant Configuration
|
# Assistant Configuration
|
||||||
|
|
||||||
This file contains configuration and commands for the Claude assistant working on the CMtec CMDR Joystick 25.
|
|
||||||
|
|
||||||
## Global Rules
|
## Global Rules
|
||||||
|
|
||||||
|
- Rust emdedded
|
||||||
- Always describe what you thinking and your plan befor starting to change files.
|
- Always describe what you thinking and your plan befor starting to change files.
|
||||||
- Make sure code have max 5 indentation levels
|
- Make sure code have max 5 indentation levels
|
||||||
- Use classes, arrays, structs, etc for clean organization
|
- Use arrays, structs, etc for clean organization
|
||||||
- Make sure the codebase is manageable and easily readable
|
- Make sure the codebase is manageable and easily readable
|
||||||
- Always check code (compile/check)
|
- Always check code (compile/check)
|
||||||
- Always fix compile warnings
|
- Always fix compile warnings
|
||||||
- Do not try to deploy project to hardware
|
- Do not try to deploy project to hardware
|
||||||
- Remember to update CLAUDE.md about current progress, notes and recent changes. But always wait for confirmation that the code work as intended.
|
- Use "just" for check, test, flash etc
|
||||||
|
- Use file structure described in this file
|
||||||
|
|
||||||
|
## Firmware File Structure Blueprint (RP2040 / RP2350)
|
||||||
|
|
||||||
|
- `src/hardware.rs` — **Required.** Centralize pin assignments, clock constants, peripheral aliases, timer intervals, and other board-specific configuration. Nothing outside this module hardcodes MCU pin numbers or magic frequencies.
|
||||||
|
- `src/board.rs` — **Required.** Board bring-up; owns peripheral wiring (clocks, GPIO, comms, sensors, USB), exposes `Board`/`BoardParts` (or equivalent). Keep granular comments explaining each hardware init block.
|
||||||
|
- `src/main.rs` — **Required.** Thin firmware entry; fetch initialized parts, load persisted configuration, configure timers, and run the primary control loop (USB/event poll, scheduling, report generation). Runtime orchestration only.
|
||||||
|
- Feature modules stay single-purpose (e.g., `inputs.rs`, `sensors.rs`, `storage.rs`, `status.rs`, `usb_report.rs`, `usb_device.rs`). Each should include unit tests with short intent comments capturing edge cases and data packing, runnable in host mode.
|
||||||
|
- Utility crates (`mapping.rs`, `calibration.rs`, etc.) should avoid cross-module side effects—prefer explicit data passed through `BoardParts`/state structs.
|
||||||
|
- Comments document why a block exists or which hardware behaviour it mirrors; avoid repeating obvious code but provide enough context for re-use across RP-series projects.
|
||||||
|
|||||||
72
Justfile
Normal file
72
Justfile
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
set export := true
|
||||||
|
|
||||||
|
default: deps check
|
||||||
|
|
||||||
|
# Check and install dependencies via rustup
|
||||||
|
deps:
|
||||||
|
@just _setup-rustup
|
||||||
|
@just _setup-rust-toolchain
|
||||||
|
@just _setup-targets
|
||||||
|
@just _setup-cargo-binutils
|
||||||
|
@just _check-python
|
||||||
|
|
||||||
|
_setup-rustup:
|
||||||
|
@if ! command -v rustup >/dev/null 2>&1; then \
|
||||||
|
echo "Installing rustup..."; \
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
|
||||||
|
echo "Please run: source ~/.cargo/env"; \
|
||||||
|
echo "Then run 'just deps' again"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_setup-rust-toolchain:
|
||||||
|
@if ! rustc -vV >/dev/null 2>&1; then \
|
||||||
|
echo "Installing stable Rust toolchain..."; \
|
||||||
|
rustup default stable; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_setup-targets:
|
||||||
|
@if ! rustup target list --installed | grep -q "thumbv6m-none-eabi"; then \
|
||||||
|
echo "Installing thumbv6m-none-eabi target..."; \
|
||||||
|
rustup target add thumbv6m-none-eabi; \
|
||||||
|
fi
|
||||||
|
@if ! rustup component list --installed | grep -q "llvm-tools"; then \
|
||||||
|
echo "Installing llvm-tools component..."; \
|
||||||
|
rustup component add llvm-tools; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_setup-cargo-binutils:
|
||||||
|
@if ! cargo objcopy --version >/dev/null 2>&1; then \
|
||||||
|
echo "Installing cargo-binutils..."; \
|
||||||
|
cargo install cargo-binutils; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
_check-python:
|
||||||
|
@command -v python3 >/dev/null 2>&1 || (echo "Missing: python3 - please install via nix" && exit 1)
|
||||||
|
|
||||||
|
check: deps
|
||||||
|
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-joystick.bin
|
||||||
|
cd rp2040 && python3 uf2conv.py target/thumbv6m-none-eabi/release/cmdr-joystick.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/"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# CMDR Joystick 25
|
# CMDR Joystick
|
||||||
|
|
||||||
USB HID joystick firmware + hardware: 2 hall‑effect gimbals, 2 physical hat
|
USB HID joystick firmware + hardware: 2 hall‑effect gimbals, 2 physical hat
|
||||||
switches, and a 5x5 button matrix (plus 2 extra buttons). The firmware exposes
|
switches, and a 5x5 button matrix (plus 2 extra buttons). The firmware exposes
|
||||||
@ -96,6 +96,8 @@ Config Layer (holding CONFIG button)
|
|||||||
|
|
||||||
- USB interrupt endpoint configured for 1 ms poll interval (1 kHz reports)
|
- USB interrupt endpoint configured for 1 ms poll interval (1 kHz reports)
|
||||||
- Input scan, smoothing, processing, and mapping now execute back-to-back
|
- Input scan, smoothing, processing, and mapping now execute back-to-back
|
||||||
|
- Enhanced button debounce: 15-scan threshold (3ms) with anti-bounce protection to prevent double presses
|
||||||
|
- Smart HAT switch filtering: disables all HAT buttons when multiple directions are detected to prevent spurious inputs
|
||||||
- First activity after idle forces immediate USB packet without waiting for the next tick
|
- First activity after idle forces immediate USB packet without waiting for the next tick
|
||||||
- Existing idle timeout preserved (5 s) to avoid unnecessary host wake-ups
|
- Existing idle timeout preserved (5 s) to avoid unnecessary host wake-ups
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ Config Layer (holding CONFIG button)
|
|||||||
- 1x Bottom case (3D printed)
|
- 1x Bottom case (3D printed)
|
||||||
- 1x Top plate (3D printed)
|
- 1x Top plate (3D printed)
|
||||||
- 2x Hat swith top (3D printed) [stl](/mCAD/Hat_Castle_Short_scale_99_99_130.stl)
|
- 2x Hat swith top (3D printed) [stl](/mCAD/Hat_Castle_Short_scale_99_99_130.stl)
|
||||||
- 1x Custom PCB (CMDR Joystick 25 rev A)
|
- 1x Custom PCB (CMDR Joystick rev A)
|
||||||
- 
|
- 
|
||||||
- 
|
- 
|
||||||
- Gerber files: [zip](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_gerber.zip)
|
- Gerber files: [zip](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_gerber.zip)
|
||||||
|
|||||||
199525
eCAD/cmdr-joystick/RP2040_Zero.stp
Normal file
199525
eCAD/cmdr-joystick/RP2040_Zero.stp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"board": {
|
"board": {
|
||||||
"active_layer": 37,
|
"active_layer": 5,
|
||||||
"active_layer_preset": "All Layers",
|
"active_layer_preset": "",
|
||||||
"auto_track_width": false,
|
"auto_track_width": false,
|
||||||
"hidden_netclasses": [],
|
"hidden_netclasses": [],
|
||||||
"hidden_nets": [],
|
"hidden_nets": [],
|
||||||
@ -10,6 +10,7 @@
|
|||||||
"opacity": {
|
"opacity": {
|
||||||
"images": 0.6,
|
"images": 0.6,
|
||||||
"pads": 1.0,
|
"pads": 1.0,
|
||||||
|
"shapes": 1.0,
|
||||||
"tracks": 1.0,
|
"tracks": 1.0,
|
||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
@ -28,43 +29,27 @@
|
|||||||
"zones": true
|
"zones": true
|
||||||
},
|
},
|
||||||
"visible_items": [
|
"visible_items": [
|
||||||
0,
|
"vias",
|
||||||
1,
|
"footprint_text",
|
||||||
2,
|
"footprint_anchors",
|
||||||
3,
|
"ratsnest",
|
||||||
4,
|
"grid",
|
||||||
5,
|
"footprints_front",
|
||||||
8,
|
"footprints_back",
|
||||||
9,
|
"footprint_values",
|
||||||
10,
|
"footprint_references",
|
||||||
11,
|
"tracks",
|
||||||
12,
|
"drc_errors",
|
||||||
13,
|
"drawing_sheet",
|
||||||
15,
|
"bitmaps",
|
||||||
16,
|
"pads",
|
||||||
17,
|
"zones",
|
||||||
18,
|
"drc_warnings",
|
||||||
19,
|
"locked_item_shadows",
|
||||||
20,
|
"conflict_shadows",
|
||||||
21,
|
"shapes"
|
||||||
22,
|
|
||||||
23,
|
|
||||||
24,
|
|
||||||
25,
|
|
||||||
26,
|
|
||||||
27,
|
|
||||||
28,
|
|
||||||
29,
|
|
||||||
30,
|
|
||||||
32,
|
|
||||||
33,
|
|
||||||
34,
|
|
||||||
35,
|
|
||||||
36,
|
|
||||||
39,
|
|
||||||
40
|
|
||||||
],
|
],
|
||||||
"visible_layers": "fffffff_ffffffff",
|
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
|
||||||
"zone_display_mode": 0
|
"zone_display_mode": 0
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
@ -75,9 +60,72 @@
|
|||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"filename": "cmdr-joystick.kicad_prl",
|
"filename": "cmdr-joystick.kicad_prl",
|
||||||
"version": 3
|
"version": 5
|
||||||
},
|
},
|
||||||
|
"net_inspector_panel": {
|
||||||
|
"col_hidden": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"col_order": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"col_widths": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"custom_group_rules": [],
|
||||||
|
"expanded_rows": [],
|
||||||
|
"filter_by_net_name": true,
|
||||||
|
"filter_by_netclass": true,
|
||||||
|
"filter_text": "",
|
||||||
|
"group_by_constraint": false,
|
||||||
|
"group_by_netclass": false,
|
||||||
|
"show_unconnected_nets": false,
|
||||||
|
"show_zero_pad_nets": false,
|
||||||
|
"sort_ascending": true,
|
||||||
|
"sorting_column": 0
|
||||||
|
},
|
||||||
|
"open_jobsets": [],
|
||||||
"project": {
|
"project": {
|
||||||
"files": []
|
"files": []
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"selection_filter": {
|
||||||
|
"graphics": true,
|
||||||
|
"images": true,
|
||||||
|
"labels": true,
|
||||||
|
"lockedItems": false,
|
||||||
|
"otherItems": true,
|
||||||
|
"pins": true,
|
||||||
|
"symbols": true,
|
||||||
|
"text": true,
|
||||||
|
"wires": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,17 @@
|
|||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"board_outline_line_width": 0.09999999999999999,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"copper_line_width": 0.19999999999999998,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
|
"apply_defaults_to_fp_text": false,
|
||||||
|
"board_outline_line_width": 0.1,
|
||||||
|
"copper_line_width": 0.2,
|
||||||
"copper_text_italic": false,
|
"copper_text_italic": false,
|
||||||
"copper_text_size_h": 1.5,
|
"copper_text_size_h": 1.5,
|
||||||
"copper_text_size_v": 1.5,
|
"copper_text_size_v": 1.5,
|
||||||
"copper_text_thickness": 0.3,
|
"copper_text_thickness": 0.3,
|
||||||
"copper_text_upright": false,
|
"copper_text_upright": false,
|
||||||
"courtyard_line_width": 0.049999999999999996,
|
"courtyard_line_width": 0.05,
|
||||||
"dimension_precision": 4,
|
"dimension_precision": 4,
|
||||||
"dimension_units": 3,
|
"dimension_units": 3,
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
@ -21,7 +24,7 @@
|
|||||||
"text_position": 0,
|
"text_position": 0,
|
||||||
"units_format": 1
|
"units_format": 1
|
||||||
},
|
},
|
||||||
"fab_line_width": 0.09999999999999999,
|
"fab_line_width": 0.1,
|
||||||
"fab_text_italic": false,
|
"fab_text_italic": false,
|
||||||
"fab_text_size_h": 1.0,
|
"fab_text_size_h": 1.0,
|
||||||
"fab_text_size_v": 1.0,
|
"fab_text_size_v": 1.0,
|
||||||
@ -66,15 +69,20 @@
|
|||||||
"copper_edge_clearance": "error",
|
"copper_edge_clearance": "error",
|
||||||
"copper_sliver": "warning",
|
"copper_sliver": "warning",
|
||||||
"courtyards_overlap": "error",
|
"courtyards_overlap": "error",
|
||||||
|
"creepage": "error",
|
||||||
"diff_pair_gap_out_of_range": "error",
|
"diff_pair_gap_out_of_range": "error",
|
||||||
"diff_pair_uncoupled_length_too_long": "error",
|
"diff_pair_uncoupled_length_too_long": "error",
|
||||||
"drill_out_of_range": "error",
|
"drill_out_of_range": "error",
|
||||||
"duplicate_footprints": "warning",
|
"duplicate_footprints": "warning",
|
||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "ignore",
|
"footprint_type_mismatch": "ignore",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
"hole_near_hole": "error",
|
"hole_near_hole": "error",
|
||||||
|
"hole_to_hole": "error",
|
||||||
|
"holes_co_located": "warning",
|
||||||
"invalid_outline": "error",
|
"invalid_outline": "error",
|
||||||
"isolated_copper": "warning",
|
"isolated_copper": "warning",
|
||||||
"item_on_disabled_layer": "error",
|
"item_on_disabled_layer": "error",
|
||||||
@ -84,9 +92,11 @@
|
|||||||
"lib_footprint_mismatch": "warning",
|
"lib_footprint_mismatch": "warning",
|
||||||
"malformed_courtyard": "error",
|
"malformed_courtyard": "error",
|
||||||
"microvia_drill_out_of_range": "error",
|
"microvia_drill_out_of_range": "error",
|
||||||
|
"mirrored_text_on_front_layer": "warning",
|
||||||
"missing_courtyard": "ignore",
|
"missing_courtyard": "ignore",
|
||||||
"missing_footprint": "warning",
|
"missing_footprint": "warning",
|
||||||
"net_conflict": "warning",
|
"net_conflict": "warning",
|
||||||
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
"npth_inside_courtyard": "ignore",
|
"npth_inside_courtyard": "ignore",
|
||||||
"padstack": "warning",
|
"padstack": "warning",
|
||||||
"pth_inside_courtyard": "ignore",
|
"pth_inside_courtyard": "ignore",
|
||||||
@ -98,10 +108,13 @@
|
|||||||
"solder_mask_bridge": "error",
|
"solder_mask_bridge": "error",
|
||||||
"starved_thermal": "error",
|
"starved_thermal": "error",
|
||||||
"text_height": "warning",
|
"text_height": "warning",
|
||||||
|
"text_on_edge_cuts": "error",
|
||||||
"text_thickness": "warning",
|
"text_thickness": "warning",
|
||||||
"through_hole_pad_without_hole": "error",
|
"through_hole_pad_without_hole": "error",
|
||||||
"too_many_vias": "error",
|
"too_many_vias": "error",
|
||||||
|
"track_angle": "error",
|
||||||
"track_dangling": "warning",
|
"track_dangling": "warning",
|
||||||
|
"track_segment_length": "error",
|
||||||
"track_width": "error",
|
"track_width": "error",
|
||||||
"tracks_crossing": "error",
|
"tracks_crossing": "error",
|
||||||
"unconnected_items": "error",
|
"unconnected_items": "error",
|
||||||
@ -114,59 +127,64 @@
|
|||||||
"min_clearance": 0.0,
|
"min_clearance": 0.0,
|
||||||
"min_connection": 0.0,
|
"min_connection": 0.0,
|
||||||
"min_copper_edge_clearance": 0.0,
|
"min_copper_edge_clearance": 0.0,
|
||||||
|
"min_groove_width": 0.0,
|
||||||
"min_hole_clearance": 0.25,
|
"min_hole_clearance": 0.25,
|
||||||
"min_hole_to_hole": 0.25,
|
"min_hole_to_hole": 0.25,
|
||||||
"min_microvia_diameter": 0.19999999999999998,
|
"min_microvia_diameter": 0.2,
|
||||||
"min_microvia_drill": 0.09999999999999999,
|
"min_microvia_drill": 0.1,
|
||||||
"min_resolved_spokes": 2,
|
"min_resolved_spokes": 2,
|
||||||
"min_silk_clearance": 0.0,
|
"min_silk_clearance": 0.0,
|
||||||
"min_text_height": 0.7999999999999999,
|
"min_text_height": 0.8,
|
||||||
"min_text_thickness": 0.08,
|
"min_text_thickness": 0.08,
|
||||||
"min_through_hole_diameter": 0.3,
|
"min_through_hole_diameter": 0.3,
|
||||||
"min_track_width": 0.0,
|
"min_track_width": 0.0,
|
||||||
"min_via_annular_width": 0.09999999999999999,
|
"min_via_annular_width": 0.1,
|
||||||
"min_via_diameter": 0.5,
|
"min_via_diameter": 0.5,
|
||||||
"solder_mask_clearance": 0.0,
|
"solder_mask_clearance": 0.0,
|
||||||
"solder_mask_min_width": 0.0,
|
"solder_mask_min_width": 0.0,
|
||||||
"solder_mask_to_copper_clearance": 0.0,
|
"solder_mask_to_copper_clearance": 0.005,
|
||||||
"use_height_for_length_calcs": true
|
"use_height_for_length_calcs": true
|
||||||
},
|
},
|
||||||
"teardrop_options": [
|
"teardrop_options": [
|
||||||
{
|
{
|
||||||
"td_allow_use_two_tracks": true,
|
"td_onpthpad": true,
|
||||||
"td_curve_segcount": 5,
|
|
||||||
"td_on_pad_in_zone": false,
|
|
||||||
"td_onpadsmd": true,
|
|
||||||
"td_onroundshapesonly": false,
|
"td_onroundshapesonly": false,
|
||||||
|
"td_onsmdpad": true,
|
||||||
"td_ontrackend": false,
|
"td_ontrackend": false,
|
||||||
"td_onviapad": true
|
"td_onvia": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"teardrop_parameters": [
|
"teardrop_parameters": [
|
||||||
{
|
{
|
||||||
"td_curve_segcount": 0,
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 1,
|
||||||
"td_height_ratio": 1.0,
|
"td_height_ratio": 1.0,
|
||||||
"td_length_ratio": 0.5,
|
"td_length_ratio": 0.5,
|
||||||
"td_maxheight": 2.0,
|
"td_maxheight": 2.0,
|
||||||
"td_maxlen": 1.0,
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
"td_target_name": "td_round_shape",
|
"td_target_name": "td_round_shape",
|
||||||
"td_width_to_size_filter_ratio": 0.9
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"td_curve_segcount": 0,
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 1,
|
||||||
"td_height_ratio": 1.0,
|
"td_height_ratio": 1.0,
|
||||||
"td_length_ratio": 0.5,
|
"td_length_ratio": 0.5,
|
||||||
"td_maxheight": 2.0,
|
"td_maxheight": 2.0,
|
||||||
"td_maxlen": 1.0,
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
"td_target_name": "td_rect_shape",
|
"td_target_name": "td_rect_shape",
|
||||||
"td_width_to_size_filter_ratio": 0.9
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"td_curve_segcount": 0,
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 1,
|
||||||
"td_height_ratio": 1.0,
|
"td_height_ratio": 1.0,
|
||||||
"td_length_ratio": 0.5,
|
"td_length_ratio": 0.5,
|
||||||
"td_maxheight": 2.0,
|
"td_maxheight": 2.0,
|
||||||
"td_maxlen": 1.0,
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
"td_target_name": "td_track_end",
|
"td_target_name": "td_track_end",
|
||||||
"td_width_to_size_filter_ratio": 0.9
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
}
|
}
|
||||||
@ -174,6 +192,32 @@
|
|||||||
"track_widths": [
|
"track_widths": [
|
||||||
0.0
|
0.0
|
||||||
],
|
],
|
||||||
|
"tuning_pattern_settings": {
|
||||||
|
"diff_pair_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 1.0
|
||||||
|
},
|
||||||
|
"diff_pair_skew_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
},
|
||||||
|
"single_track_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
"via_dimensions": [
|
"via_dimensions": [
|
||||||
{
|
{
|
||||||
"diameter": 0.0,
|
"diameter": 0.0,
|
||||||
@ -189,6 +233,7 @@
|
|||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": ""
|
||||||
},
|
},
|
||||||
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
@ -383,10 +428,15 @@
|
|||||||
"duplicate_sheet_names": "error",
|
"duplicate_sheet_names": "error",
|
||||||
"endpoint_off_grid": "warning",
|
"endpoint_off_grid": "warning",
|
||||||
"extra_units": "error",
|
"extra_units": "error",
|
||||||
|
"footprint_filter": "ignore",
|
||||||
|
"footprint_link_issues": "warning",
|
||||||
|
"four_way_junction": "ignore",
|
||||||
"global_label_dangling": "warning",
|
"global_label_dangling": "warning",
|
||||||
"hier_label_mismatch": "error",
|
"hier_label_mismatch": "error",
|
||||||
"label_dangling": "error",
|
"label_dangling": "error",
|
||||||
|
"label_multiple_wires": "warning",
|
||||||
"lib_symbol_issues": "warning",
|
"lib_symbol_issues": "warning",
|
||||||
|
"lib_symbol_mismatch": "warning",
|
||||||
"missing_bidi_pin": "warning",
|
"missing_bidi_pin": "warning",
|
||||||
"missing_input_pin": "warning",
|
"missing_input_pin": "warning",
|
||||||
"missing_power_pin": "error",
|
"missing_power_pin": "error",
|
||||||
@ -399,9 +449,15 @@
|
|||||||
"pin_not_driven": "error",
|
"pin_not_driven": "error",
|
||||||
"pin_to_pin": "warning",
|
"pin_to_pin": "warning",
|
||||||
"power_pin_not_driven": "error",
|
"power_pin_not_driven": "error",
|
||||||
|
"same_local_global_label": "warning",
|
||||||
|
"similar_label_and_power": "warning",
|
||||||
"similar_labels": "warning",
|
"similar_labels": "warning",
|
||||||
|
"similar_power": "warning",
|
||||||
"simulation_model_issue": "error",
|
"simulation_model_issue": "error",
|
||||||
|
"single_global_label": "ignore",
|
||||||
"unannotated": "error",
|
"unannotated": "error",
|
||||||
|
"unconnected_wire_endpoint": "warning",
|
||||||
|
"undefined_netclass": "error",
|
||||||
"unit_value_mismatch": "error",
|
"unit_value_mismatch": "error",
|
||||||
"unresolved_variable": "error",
|
"unresolved_variable": "error",
|
||||||
"wire_dangling": "error"
|
"wire_dangling": "error"
|
||||||
@ -413,7 +469,7 @@
|
|||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"filename": "cmdr-joystick.kicad_pro",
|
"filename": "cmdr-joystick.kicad_pro",
|
||||||
"version": 1
|
"version": 3
|
||||||
},
|
},
|
||||||
"net_settings": {
|
"net_settings": {
|
||||||
"classes": [
|
"classes": [
|
||||||
@ -428,6 +484,7 @@
|
|||||||
"microvia_drill": 0.1,
|
"microvia_drill": 0.1,
|
||||||
"name": "Default",
|
"name": "Default",
|
||||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.25,
|
"track_width": 0.25,
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
@ -445,6 +502,7 @@
|
|||||||
"microvia_drill": 0.1,
|
"microvia_drill": 0.1,
|
||||||
"name": "+3.3V",
|
"name": "+3.3V",
|
||||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 0,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.5,
|
"track_width": 0.5,
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
@ -462,6 +520,7 @@
|
|||||||
"microvia_drill": 0.1,
|
"microvia_drill": 0.1,
|
||||||
"name": "GND",
|
"name": "GND",
|
||||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 1,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.5,
|
"track_width": 0.5,
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
@ -470,7 +529,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 3
|
"version": 4
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@ -505,6 +564,78 @@
|
|||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
"annotate_start_num": 0,
|
"annotate_start_num": 0,
|
||||||
|
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||||
|
"bom_fmt_presets": [],
|
||||||
|
"bom_fmt_settings": {
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"keep_line_breaks": false,
|
||||||
|
"keep_tabs": false,
|
||||||
|
"name": "CSV",
|
||||||
|
"ref_delimiter": ",",
|
||||||
|
"ref_range_delimiter": "",
|
||||||
|
"string_delimiter": "\""
|
||||||
|
},
|
||||||
|
"bom_presets": [],
|
||||||
|
"bom_settings": {
|
||||||
|
"exclude_dnp": false,
|
||||||
|
"fields_ordered": [
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Reference",
|
||||||
|
"name": "Reference",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Qty",
|
||||||
|
"name": "${QUANTITY}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Value",
|
||||||
|
"name": "Value",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "DNP",
|
||||||
|
"name": "${DNP}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from BOM",
|
||||||
|
"name": "${EXCLUDE_FROM_BOM}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from Board",
|
||||||
|
"name": "${EXCLUDE_FROM_BOARD}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Footprint",
|
||||||
|
"name": "Footprint",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Datasheet",
|
||||||
|
"name": "Datasheet",
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filter_string": "",
|
||||||
|
"group_symbols": true,
|
||||||
|
"include_excluded_from_bom": true,
|
||||||
|
"name": "Default Editing",
|
||||||
|
"sort_asc": true,
|
||||||
|
"sort_field": "Reference"
|
||||||
|
},
|
||||||
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
"dashed_lines_gap_length_ratio": 3.0,
|
"dashed_lines_gap_length_ratio": 3.0,
|
||||||
@ -518,6 +649,11 @@
|
|||||||
"intersheets_ref_suffix": "",
|
"intersheets_ref_suffix": "",
|
||||||
"junction_size_choice": 3,
|
"junction_size_choice": 3,
|
||||||
"label_size_ratio": 0.375,
|
"label_size_ratio": 0.375,
|
||||||
|
"operating_point_overlay_i_precision": 3,
|
||||||
|
"operating_point_overlay_i_range": "~A",
|
||||||
|
"operating_point_overlay_v_precision": 3,
|
||||||
|
"operating_point_overlay_v_range": "~V",
|
||||||
|
"overbar_offset_ratio": 1.23,
|
||||||
"pin_symbol_size": 25.0,
|
"pin_symbol_size": 25.0,
|
||||||
"text_offset_ratio": 0.15
|
"text_offset_ratio": 0.15
|
||||||
},
|
},
|
||||||
@ -529,10 +665,12 @@
|
|||||||
"net_format_name": "",
|
"net_format_name": "",
|
||||||
"page_layout_descr_file": "",
|
"page_layout_descr_file": "",
|
||||||
"plot_directory": "",
|
"plot_directory": "",
|
||||||
|
"space_save_all_events": true,
|
||||||
"spice_current_sheet_as_root": false,
|
"spice_current_sheet_as_root": false,
|
||||||
"spice_external_command": "spice \"%I\"",
|
"spice_external_command": "spice \"%I\"",
|
||||||
"spice_model_current_sheet_as_root": true,
|
"spice_model_current_sheet_as_root": true,
|
||||||
"spice_save_all_currents": false,
|
"spice_save_all_currents": false,
|
||||||
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"subpart_first_id": 65,
|
||||||
"subpart_id_separator": 0
|
"subpart_id_separator": 0
|
||||||
@ -540,7 +678,7 @@
|
|||||||
"sheets": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
"5b501981-46e2-4084-afad-38073ca78ebd",
|
"5b501981-46e2-4084-afad-38073ca78ebd",
|
||||||
""
|
"Root"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"text_variables": {}
|
"text_variables": {}
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
(kicad_sch
|
(kicad_sch
|
||||||
(version 20231120)
|
(version 20250114)
|
||||||
(generator "eeschema")
|
(generator "eeschema")
|
||||||
(generator_version "8.0")
|
(generator_version "9.0")
|
||||||
(uuid "5b501981-46e2-4084-afad-38073ca78ebd")
|
(uuid "5b501981-46e2-4084-afad-38073ca78ebd")
|
||||||
(paper "A4")
|
(paper "A4")
|
||||||
(lib_symbols
|
(lib_symbols
|
||||||
(symbol "Connector_Generic:Conn_01x03"
|
(symbol "Connector_Generic:Conn_01x03"
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 1.016) hide)
|
(offset 1.016)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -74,8 +76,19 @@
|
|||||||
)
|
)
|
||||||
(symbol "Conn_01x03_1_1"
|
(symbol "Conn_01x03_1_1"
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -2.413)
|
(start -1.27 3.81)
|
||||||
(end 0 -2.667)
|
(end 1.27 -3.81)
|
||||||
|
(stroke
|
||||||
|
(width 0.254)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type background)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 2.667)
|
||||||
|
(end 0 2.413)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -96,8 +109,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 2.667)
|
(start -1.27 -2.413)
|
||||||
(end 0 2.413)
|
(end 0 -2.667)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -106,17 +119,6 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
|
||||||
(start -1.27 3.81)
|
|
||||||
(end 1.27 -3.81)
|
|
||||||
(stroke
|
|
||||||
(width 0.254)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type background)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at -5.08 2.54 0)
|
(at -5.08 2.54 0)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
@ -172,10 +174,13 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Connector_Generic:Conn_01x04"
|
(symbol "Connector_Generic:Conn_01x04"
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 1.016) hide)
|
(offset 1.016)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -242,19 +247,19 @@
|
|||||||
)
|
)
|
||||||
(symbol "Conn_01x04_1_1"
|
(symbol "Conn_01x04_1_1"
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -4.953)
|
(start -1.27 3.81)
|
||||||
(end 0 -5.207)
|
(end 1.27 -6.35)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.254)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
(type none)
|
(type background)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -2.413)
|
(start -1.27 2.667)
|
||||||
(end 0 -2.667)
|
(end 0 2.413)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -275,8 +280,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 2.667)
|
(start -1.27 -2.413)
|
||||||
(end 0 2.413)
|
(end 0 -2.667)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -286,14 +291,14 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 3.81)
|
(start -1.27 -4.953)
|
||||||
(end 1.27 -6.35)
|
(end 0 -5.207)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
(type background)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
(pin passive line
|
||||||
@ -369,10 +374,13 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Connector_Generic:Conn_01x06"
|
(symbol "Connector_Generic:Conn_01x06"
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 1.016) hide)
|
(offset 1.016)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -439,41 +447,19 @@
|
|||||||
)
|
)
|
||||||
(symbol "Conn_01x06_1_1"
|
(symbol "Conn_01x06_1_1"
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -7.493)
|
(start -1.27 6.35)
|
||||||
(end 0 -7.747)
|
(end 1.27 -8.89)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.254)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
(type none)
|
(type background)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -4.953)
|
(start -1.27 5.207)
|
||||||
(end 0 -5.207)
|
(end 0 4.953)
|
||||||
(stroke
|
|
||||||
(width 0.1524)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(rectangle
|
|
||||||
(start -1.27 -2.413)
|
|
||||||
(end 0 -2.667)
|
|
||||||
(stroke
|
|
||||||
(width 0.1524)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(rectangle
|
|
||||||
(start -1.27 0.127)
|
|
||||||
(end 0 -0.127)
|
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -494,8 +480,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 5.207)
|
(start -1.27 0.127)
|
||||||
(end 0 4.953)
|
(end 0 -0.127)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -505,14 +491,36 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 6.35)
|
(start -1.27 -2.413)
|
||||||
(end 1.27 -8.89)
|
(end 0 -2.667)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
(type background)
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 -4.953)
|
||||||
|
(end 0 -5.207)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 -7.493)
|
||||||
|
(end 0 -7.747)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
(pin passive line
|
||||||
@ -624,10 +632,213 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
|
(symbol "Connector_Generic:Conn_02x02_Odd_Even"
|
||||||
|
(pin_names
|
||||||
|
(offset 1.016)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(property "Reference" "J"
|
||||||
|
(at 1.27 2.54 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "Conn_02x02_Odd_Even"
|
||||||
|
(at 1.27 -5.08 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "~"
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" "Generic connector, double row, 02x02, odd/even pin numbering scheme (row 1 odd numbers, row 2 even numbers), script generated (kicad-library-utils/schlib/autogen/connector/)"
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "ki_keywords" "connector"
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "ki_fp_filters" "Connector*:*_2x??_*"
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "Conn_02x02_Odd_Even_1_1"
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 1.27)
|
||||||
|
(end 3.81 -3.81)
|
||||||
|
(stroke
|
||||||
|
(width 0.254)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type background)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 0.127)
|
||||||
|
(end 0 -0.127)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 -2.413)
|
||||||
|
(end 0 -2.667)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 3.81 0.127)
|
||||||
|
(end 2.54 -0.127)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 3.81 -2.413)
|
||||||
|
(end 2.54 -2.667)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at -5.08 0 0)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at -5.08 -2.54 0)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_3"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "3"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 7.62 0 180)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 7.62 -2.54 180)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_4"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "4"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Connector_Generic:Conn_02x05_Odd_Even"
|
(symbol "Connector_Generic:Conn_02x05_Odd_Even"
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 1.016) hide)
|
(offset 1.016)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -694,30 +905,19 @@
|
|||||||
)
|
)
|
||||||
(symbol "Conn_02x05_Odd_Even_1_1"
|
(symbol "Conn_02x05_Odd_Even_1_1"
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -4.953)
|
(start -1.27 6.35)
|
||||||
(end 0 -5.207)
|
(end 3.81 -6.35)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.254)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
(type none)
|
(type background)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 -2.413)
|
(start -1.27 5.207)
|
||||||
(end 0 -2.667)
|
(end 0 4.953)
|
||||||
(stroke
|
|
||||||
(width 0.1524)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(rectangle
|
|
||||||
(start -1.27 0.127)
|
|
||||||
(end 0 -0.127)
|
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -738,8 +938,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 5.207)
|
(start -1.27 0.127)
|
||||||
(end 0 4.953)
|
(end 0 -0.127)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -749,19 +949,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start -1.27 6.35)
|
(start -1.27 -2.413)
|
||||||
(end 3.81 -6.35)
|
(end 0 -2.667)
|
||||||
(stroke
|
|
||||||
(width 0.254)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type background)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(rectangle
|
|
||||||
(start 3.81 -4.953)
|
|
||||||
(end 2.54 -5.207)
|
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -771,8 +960,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start 3.81 -2.413)
|
(start -1.27 -4.953)
|
||||||
(end 2.54 -2.667)
|
(end 0 -5.207)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -782,8 +971,8 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start 3.81 0.127)
|
(start 3.81 5.207)
|
||||||
(end 2.54 -0.127)
|
(end 2.54 4.953)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -804,8 +993,30 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(rectangle
|
(rectangle
|
||||||
(start 3.81 5.207)
|
(start 3.81 0.127)
|
||||||
(end 2.54 4.953)
|
(end 2.54 -0.127)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 3.81 -2.413)
|
||||||
|
(end 2.54 -2.667)
|
||||||
|
(stroke
|
||||||
|
(width 0.1524)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 3.81 -4.953)
|
||||||
|
(end 2.54 -5.207)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.1524)
|
(width 0.1524)
|
||||||
(type default)
|
(type default)
|
||||||
@ -832,42 +1043,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
|
||||||
(at 7.62 -5.08 180)
|
|
||||||
(length 3.81)
|
|
||||||
(name "Pin_10"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "10"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
|
||||||
(at 7.62 5.08 180)
|
|
||||||
(length 3.81)
|
|
||||||
(name "Pin_2"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "2"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at -5.08 2.54 0)
|
(at -5.08 2.54 0)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
@ -886,24 +1061,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
|
||||||
(at 7.62 2.54 180)
|
|
||||||
(length 3.81)
|
|
||||||
(name "Pin_4"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "4"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at -5.08 0 0)
|
(at -5.08 0 0)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
@ -922,24 +1079,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
|
||||||
(at 7.62 0 180)
|
|
||||||
(length 3.81)
|
|
||||||
(name "Pin_6"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "6"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at -5.08 -2.54 0)
|
(at -5.08 -2.54 0)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
@ -958,6 +1097,78 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at -5.08 -5.08 0)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_9"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "9"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 7.62 5.08 180)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 7.62 2.54 180)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_4"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "4"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 7.62 0 180)
|
||||||
|
(length 3.81)
|
||||||
|
(name "Pin_6"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "6"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at 7.62 -2.54 180)
|
(at 7.62 -2.54 180)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
@ -977,16 +1188,16 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at -5.08 -5.08 0)
|
(at 7.62 -5.08 180)
|
||||||
(length 3.81)
|
(length 3.81)
|
||||||
(name "Pin_9"
|
(name "Pin_10"
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(number "9"
|
(number "10"
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -995,9 +1206,12 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Device:C"
|
(symbol "Device:C"
|
||||||
(pin_numbers hide)
|
(pin_numbers
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 0.254)
|
(offset 0.254)
|
||||||
)
|
)
|
||||||
@ -1070,7 +1284,7 @@
|
|||||||
(symbol "C_0_1"
|
(symbol "C_0_1"
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy -2.032 -0.762) (xy 2.032 -0.762)
|
(xy -2.032 0.762) (xy 2.032 0.762)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.508)
|
(width 0.508)
|
||||||
@ -1082,7 +1296,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy -2.032 0.762) (xy 2.032 0.762)
|
(xy -2.032 -0.762) (xy 2.032 -0.762)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.508)
|
(width 0.508)
|
||||||
@ -1131,9 +1345,12 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Device:R"
|
(symbol "Device:R"
|
||||||
(pin_numbers hide)
|
(pin_numbers
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(pin_names
|
(pin_names
|
||||||
(offset 0)
|
(offset 0)
|
||||||
)
|
)
|
||||||
@ -1252,10 +1469,15 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Diode:1N4148W"
|
(symbol "Diode:1N4148W"
|
||||||
(pin_numbers hide)
|
(pin_numbers
|
||||||
(pin_names hide)
|
(hide yes)
|
||||||
|
)
|
||||||
|
(pin_names
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -1351,10 +1573,10 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 1.27 0) (xy -1.27 0)
|
(xy 1.27 1.27) (xy 1.27 -1.27) (xy -1.27 0) (xy 1.27 1.27)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0)
|
(width 0.254)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
@ -1363,10 +1585,10 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 1.27 1.27) (xy 1.27 -1.27) (xy -1.27 0) (xy 1.27 1.27)
|
(xy 1.27 0) (xy -1.27 0)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0)
|
||||||
(type default)
|
(type default)
|
||||||
)
|
)
|
||||||
(fill
|
(fill
|
||||||
@ -1412,6 +1634,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Mechanical:MountingHole"
|
(symbol "Mechanical:MountingHole"
|
||||||
(pin_names
|
(pin_names
|
||||||
@ -1494,6 +1717,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Memory_EEPROM:M24C02-FMN"
|
(symbol "Memory_EEPROM:M24C02-FMN"
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
@ -1628,6 +1852,24 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(pin power_in line
|
||||||
|
(at 0 7.62 270)
|
||||||
|
(length 2.54)
|
||||||
|
(name "VCC"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "8"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(pin power_in line
|
(pin power_in line
|
||||||
(at 0 -7.62 90)
|
(at 0 -7.62 90)
|
||||||
(length 2.54)
|
(length 2.54)
|
||||||
@ -1700,28 +1942,13 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin power_in line
|
|
||||||
(at 0 7.62 270)
|
|
||||||
(length 2.54)
|
|
||||||
(name "VCC"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "8"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Transistor_FET:2N7002E"
|
(symbol "Transistor_FET:2N7002E"
|
||||||
(pin_names hide)
|
(pin_names
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -1792,18 +2019,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(symbol "2N7002E_0_1"
|
(symbol "2N7002E_0_1"
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 0.254 0) (xy -2.54 0)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.254 1.905) (xy 0.254 -1.905)
|
(xy 0.254 1.905) (xy 0.254 -1.905)
|
||||||
@ -1818,7 +2033,19 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.762 -1.27) (xy 0.762 -2.286)
|
(xy 0.254 0) (xy -2.54 0)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 0.762 2.286) (xy 0.762 1.27)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.254)
|
||||||
@ -1842,7 +2069,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.762 2.286) (xy 0.762 1.27)
|
(xy 0.762 -1.27) (xy 0.762 -2.286)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.254)
|
||||||
@ -1852,30 +2079,6 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 2.54 2.54) (xy 2.54 1.778)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.762 -1.778) (xy 3.302 -1.778) (xy 3.302 1.778) (xy 0.762 1.778)
|
(xy 0.762 -1.778) (xy 3.302 -1.778) (xy 3.302 1.778) (xy 0.762 1.778)
|
||||||
@ -1900,6 +2103,63 @@
|
|||||||
(type outline)
|
(type outline)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(circle
|
||||||
|
(center 1.651 0)
|
||||||
|
(radius 2.794)
|
||||||
|
(stroke
|
||||||
|
(width 0.254)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 2.54 2.54) (xy 2.54 1.778)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(circle
|
||||||
|
(center 2.54 1.778)
|
||||||
|
(radius 0.254)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type outline)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(circle
|
||||||
|
(center 2.54 -1.778)
|
||||||
|
(radius 0.254)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type outline)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 2.794 0.508) (xy 2.921 0.381) (xy 3.683 0.381) (xy 3.81 0.254)
|
(xy 2.794 0.508) (xy 2.921 0.381) (xy 3.683 0.381) (xy 3.81 0.254)
|
||||||
@ -1924,39 +2184,6 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(circle
|
|
||||||
(center 1.651 0)
|
|
||||||
(radius 2.794)
|
|
||||||
(stroke
|
|
||||||
(width 0.254)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(circle
|
|
||||||
(center 2.54 -1.778)
|
|
||||||
(radius 0.254)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type outline)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(circle
|
|
||||||
(center 2.54 1.778)
|
|
||||||
(radius 0.254)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type outline)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
(symbol "2N7002E_1_1"
|
(symbol "2N7002E_1_1"
|
||||||
(pin input line
|
(pin input line
|
||||||
@ -1977,24 +2204,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
|
||||||
(at 2.54 -5.08 90)
|
|
||||||
(length 2.54)
|
|
||||||
(name "S"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "2"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at 2.54 5.08 270)
|
(at 2.54 5.08 270)
|
||||||
(length 2.54)
|
(length 2.54)
|
||||||
@ -2013,10 +2222,31 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 2.54 -5.08 90)
|
||||||
|
(length 2.54)
|
||||||
|
(name "S"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "Transistor_FET:Si2371EDS"
|
(symbol "Transistor_FET:Si2371EDS"
|
||||||
(pin_names hide)
|
(pin_names
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
(in_bom yes)
|
(in_bom yes)
|
||||||
(on_board yes)
|
(on_board yes)
|
||||||
@ -2087,6 +2317,18 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
(symbol "Si2371EDS_0_1"
|
(symbol "Si2371EDS_0_1"
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 0.254 1.905) (xy 0.254 -1.905)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0.254)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.254 0) (xy -2.54 0)
|
(xy 0.254 0) (xy -2.54 0)
|
||||||
@ -2101,7 +2343,31 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0.254 1.905) (xy 0.254 -1.905)
|
(xy 0.762 2.286) (xy 0.762 1.27)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0.254)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 0.762 1.778) (xy 3.302 1.778) (xy 3.302 -1.778) (xy 0.762 -1.778)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 0.762 0.508) (xy 0.762 -0.508)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.254)
|
||||||
@ -2123,10 +2389,9 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(polyline
|
(circle
|
||||||
(pts
|
(center 1.651 0)
|
||||||
(xy 0.762 0.508) (xy 0.762 -0.508)
|
(radius 2.794)
|
||||||
)
|
|
||||||
(stroke
|
(stroke
|
||||||
(width 0.254)
|
(width 0.254)
|
||||||
(type default)
|
(type default)
|
||||||
@ -2135,54 +2400,6 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 0.762 2.286) (xy 0.762 1.27)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0.254)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 2.54 2.54) (xy 2.54 1.778)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
|
||||||
(pts
|
|
||||||
(xy 0.762 1.778) (xy 3.302 1.778) (xy 3.302 -1.778) (xy 0.762 -1.778)
|
|
||||||
)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 2.286 0) (xy 1.27 0.381) (xy 1.27 -0.381) (xy 2.286 0)
|
(xy 2.286 0) (xy 1.27 0.381) (xy 1.27 -0.381) (xy 2.286 0)
|
||||||
@ -2195,6 +2412,52 @@
|
|||||||
(type outline)
|
(type outline)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 2.54 2.54) (xy 2.54 1.778)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(circle
|
||||||
|
(center 2.54 1.778)
|
||||||
|
(radius 0.254)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type outline)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(circle
|
||||||
|
(center 2.54 -1.778)
|
||||||
|
(radius 0.254)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type outline)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(polyline
|
||||||
|
(pts
|
||||||
|
(xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 2.794 -0.508) (xy 2.921 -0.381) (xy 3.683 -0.381) (xy 3.81 -0.254)
|
(xy 2.794 -0.508) (xy 2.921 -0.381) (xy 3.683 -0.381) (xy 3.81 -0.254)
|
||||||
@ -2219,39 +2482,6 @@
|
|||||||
(type none)
|
(type none)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(circle
|
|
||||||
(center 1.651 0)
|
|
||||||
(radius 2.794)
|
|
||||||
(stroke
|
|
||||||
(width 0.254)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type none)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(circle
|
|
||||||
(center 2.54 -1.778)
|
|
||||||
(radius 0.254)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type outline)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(circle
|
|
||||||
(center 2.54 1.778)
|
|
||||||
(radius 0.254)
|
|
||||||
(stroke
|
|
||||||
(width 0)
|
|
||||||
(type default)
|
|
||||||
)
|
|
||||||
(fill
|
|
||||||
(type outline)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
(symbol "Si2371EDS_1_1"
|
(symbol "Si2371EDS_1_1"
|
||||||
(pin input line
|
(pin input line
|
||||||
@ -2272,24 +2502,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin passive line
|
|
||||||
(at 2.54 -5.08 90)
|
|
||||||
(length 2.54)
|
|
||||||
(name "S"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "2"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin passive line
|
(pin passive line
|
||||||
(at 2.54 5.08 270)
|
(at 2.54 5.08 270)
|
||||||
(length 2.54)
|
(length 2.54)
|
||||||
@ -2308,7 +2520,26 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 2.54 -5.08 90)
|
||||||
|
(length 2.54)
|
||||||
|
(name "S"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "cmdr-joystick:rp2040zero_upside_down"
|
(symbol "cmdr-joystick:rp2040zero_upside_down"
|
||||||
(exclude_from_sim no)
|
(exclude_from_sim no)
|
||||||
@ -2389,186 +2620,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin bidirectional line
|
|
||||||
(at -13.97 -35.56 0)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP9"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "10"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at -13.97 -38.1 0)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP10"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "11"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at -13.97 -40.64 0)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP11"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "12"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at -13.97 -43.18 0)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP12"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "13"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at -13.97 -45.72 0)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP13"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "14"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -45.72 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP14"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "15"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -43.18 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP15"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "16"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -17.78 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP26"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "17"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -15.24 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP27"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "18"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -12.7 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP28"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "19"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
(pin bidirectional line
|
||||||
(at -13.97 -12.7 0)
|
(at -13.97 -12.7 0)
|
||||||
(length 2.54)
|
(length 2.54)
|
||||||
@ -2587,78 +2638,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(pin bidirectional line
|
|
||||||
(at 12.7 -10.16 180)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GP29"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "20"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin power_out line
|
|
||||||
(at 2.54 -2.54 270)
|
|
||||||
(length 2.54)
|
|
||||||
(name "3V3"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "21"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin power_out line
|
|
||||||
(at 0 -52.07 90)
|
|
||||||
(length 2.54)
|
|
||||||
(name "GND"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "22"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin power_in line
|
|
||||||
(at -2.54 -2.54 270)
|
|
||||||
(length 2.54)
|
|
||||||
(name "5V"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(number "23"
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(pin bidirectional line
|
(pin bidirectional line
|
||||||
(at -13.97 -15.24 0)
|
(at -13.97 -15.24 0)
|
||||||
(length 2.54)
|
(length 2.54)
|
||||||
@ -2785,7 +2764,260 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -13.97 -35.56 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP9"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "10"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -13.97 -38.1 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP10"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "11"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -13.97 -40.64 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP11"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "12"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -13.97 -43.18 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP12"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "13"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -13.97 -45.72 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP13"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "14"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin power_in line
|
||||||
|
(at -2.54 -2.54 270)
|
||||||
|
(length 2.54)
|
||||||
|
(name "5V"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "23"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin power_out line
|
||||||
|
(at 0 -52.07 90)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GND"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "22"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin power_out line
|
||||||
|
(at 2.54 -2.54 270)
|
||||||
|
(length 2.54)
|
||||||
|
(name "3V3"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "21"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -10.16 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP29"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "20"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -12.7 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP28"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "19"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -15.24 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP27"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "18"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -17.78 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP26"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "17"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -43.18 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP15"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "16"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at 12.7 -45.72 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GP14"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "15"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "power:+3.3V"
|
(symbol "power:+3.3V"
|
||||||
(power)
|
(power)
|
||||||
@ -2863,7 +3095,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0 0) (xy 0 2.54)
|
(xy 0 2.54) (xy 0.762 1.27)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0)
|
(width 0)
|
||||||
@ -2875,7 +3107,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0 2.54) (xy 0.762 1.27)
|
(xy 0 0) (xy 0 2.54)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0)
|
(width 0)
|
||||||
@ -2889,7 +3121,8 @@
|
|||||||
(symbol "+3.3V_1_1"
|
(symbol "+3.3V_1_1"
|
||||||
(pin power_in line
|
(pin power_in line
|
||||||
(at 0 0 90)
|
(at 0 0 90)
|
||||||
(length 0) hide
|
(length 0)
|
||||||
|
(hide yes)
|
||||||
(name "+3.3V"
|
(name "+3.3V"
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
@ -2906,6 +3139,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "power:+5V"
|
(symbol "power:+5V"
|
||||||
(power)
|
(power)
|
||||||
@ -2983,7 +3217,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0 0) (xy 0 2.54)
|
(xy 0 2.54) (xy 0.762 1.27)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0)
|
(width 0)
|
||||||
@ -2995,7 +3229,7 @@
|
|||||||
)
|
)
|
||||||
(polyline
|
(polyline
|
||||||
(pts
|
(pts
|
||||||
(xy 0 2.54) (xy 0.762 1.27)
|
(xy 0 0) (xy 0 2.54)
|
||||||
)
|
)
|
||||||
(stroke
|
(stroke
|
||||||
(width 0)
|
(width 0)
|
||||||
@ -3009,7 +3243,8 @@
|
|||||||
(symbol "+5V_1_1"
|
(symbol "+5V_1_1"
|
||||||
(pin power_in line
|
(pin power_in line
|
||||||
(at 0 0 90)
|
(at 0 0 90)
|
||||||
(length 0) hide
|
(length 0)
|
||||||
|
(hide yes)
|
||||||
(name "+5V"
|
(name "+5V"
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
@ -3026,6 +3261,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
(symbol "power:GND"
|
(symbol "power:GND"
|
||||||
(power)
|
(power)
|
||||||
@ -3105,7 +3341,8 @@
|
|||||||
(symbol "GND_1_1"
|
(symbol "GND_1_1"
|
||||||
(pin power_in line
|
(pin power_in line
|
||||||
(at 0 0 270)
|
(at 0 0 270)
|
||||||
(length 0) hide
|
(length 0)
|
||||||
|
(hide yes)
|
||||||
(name "GND"
|
(name "GND"
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
@ -3122,8 +3359,130 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(text "Gimbal Y2 (Right)"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 223.52 76.2 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "0666d517-3399-4e5a-a88e-28cb046bab53")
|
||||||
|
)
|
||||||
|
(text "Right \nHat Switch\n20-24"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 50.8 160.02 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "28c6deef-0288-400d-b950-adf2dba1adf7")
|
||||||
|
)
|
||||||
|
(text "ELRS TX\n"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 254 132.08 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "666e68ae-3562-4b09-b825-c9f5445176d4")
|
||||||
|
)
|
||||||
|
(text "Right\nTop\nButtons\n10-14\n"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 38.1 110.49 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "76352af5-c5ca-48fd-bebd-b8161cb04ff3")
|
||||||
|
)
|
||||||
|
(text "Left \nHat Switch\n15-19"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 50.8 139.7 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "921cb360-d960-4007-bfc3-e8983d17359f")
|
||||||
|
)
|
||||||
|
(text "Gimbal X2 (Right)"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 223.52 64.77 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "9ef99c88-97e7-430c-98f1-8b504d8de253")
|
||||||
|
)
|
||||||
|
(text "Left\nTop\nButtons\n5-9"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 36.83 82.55 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "9febdcbd-7a0f-4f31-b915-6903ca937f26")
|
||||||
|
)
|
||||||
|
(text "Bottom\nChassis\nButtons\n5-6"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 36.83 32.258 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "a01c3eae-3b54-4f4d-8544-9bb283418e68")
|
||||||
|
)
|
||||||
|
(text "Bottom\nChassis\nButtons\n0-4"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 36.83 54.61 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify right bottom)
|
||||||
|
)
|
||||||
|
(uuid "c7b70dbb-f435-4a57-aa9c-8cf50da328cb")
|
||||||
|
)
|
||||||
|
(text "Gimbal X1 (Left)"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 223.52 41.91 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "d87b3a29-cc14-4431-b108-be87ff886dfb")
|
||||||
|
)
|
||||||
|
(text "Gimbal Y1 (Left)"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(at 223.52 53.34 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 2.27 2.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "ed3a0309-c84e-411e-aaa8-eac48814b746")
|
||||||
|
)
|
||||||
(junction
|
(junction
|
||||||
(at 97.79 71.12)
|
(at 97.79 71.12)
|
||||||
(diameter 0)
|
(diameter 0)
|
||||||
@ -3256,6 +3615,12 @@
|
|||||||
(color 0 0 0 0)
|
(color 0 0 0 0)
|
||||||
(uuid "5b1e52ec-e5c1-4338-b386-b5c2d81ad7ed")
|
(uuid "5b1e52ec-e5c1-4338-b386-b5c2d81ad7ed")
|
||||||
)
|
)
|
||||||
|
(junction
|
||||||
|
(at 40.64 24.13)
|
||||||
|
(diameter 0)
|
||||||
|
(color 0 0 0 0)
|
||||||
|
(uuid "63844187-657a-4fea-97f7-46763266139a")
|
||||||
|
)
|
||||||
(junction
|
(junction
|
||||||
(at 41.91 50.8)
|
(at 41.91 50.8)
|
||||||
(diameter 0)
|
(diameter 0)
|
||||||
@ -3280,6 +3645,12 @@
|
|||||||
(color 0 0 0 0)
|
(color 0 0 0 0)
|
||||||
(uuid "80b9b240-83f2-4205-8a8b-dd8408d7c939")
|
(uuid "80b9b240-83f2-4205-8a8b-dd8408d7c939")
|
||||||
)
|
)
|
||||||
|
(junction
|
||||||
|
(at 92.71 45.72)
|
||||||
|
(diameter 0)
|
||||||
|
(color 0 0 0 0)
|
||||||
|
(uuid "81c28855-7c0b-4806-9f5d-0386f4f72642")
|
||||||
|
)
|
||||||
(junction
|
(junction
|
||||||
(at 92.71 73.66)
|
(at 92.71 73.66)
|
||||||
(diameter 0)
|
(diameter 0)
|
||||||
@ -3346,6 +3717,12 @@
|
|||||||
(color 0 0 0 0)
|
(color 0 0 0 0)
|
||||||
(uuid "b77ee733-b0ea-4468-af81-4eeef3e05d6c")
|
(uuid "b77ee733-b0ea-4468-af81-4eeef3e05d6c")
|
||||||
)
|
)
|
||||||
|
(junction
|
||||||
|
(at 90.17 43.18)
|
||||||
|
(diameter 0)
|
||||||
|
(color 0 0 0 0)
|
||||||
|
(uuid "b93b8bb6-b0e4-4d98-b6cc-77758c859d05")
|
||||||
|
)
|
||||||
(junction
|
(junction
|
||||||
(at 95.25 135.89)
|
(at 95.25 135.89)
|
||||||
(diameter 0)
|
(diameter 0)
|
||||||
@ -3428,10 +3805,6 @@
|
|||||||
(at 165.1 124.46)
|
(at 165.1 124.46)
|
||||||
(uuid "00ab5998-bff9-45da-8c86-2a50db2c2b90")
|
(uuid "00ab5998-bff9-45da-8c86-2a50db2c2b90")
|
||||||
)
|
)
|
||||||
(no_connect
|
|
||||||
(at 140.97 45.72)
|
|
||||||
(uuid "baa7f766-2871-41cf-81d3-67fcad28f98c")
|
|
||||||
)
|
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 218.44 132.08) (xy 224.79 132.08)
|
(xy 218.44 132.08) (xy 224.79 132.08)
|
||||||
@ -3532,6 +3905,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "0ded7b45-3a77-42b7-bea6-065ae269600e")
|
(uuid "0ded7b45-3a77-42b7-bea6-065ae269600e")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 55.88 24.13) (xy 67.31 24.13)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "0e010c6e-4690-432d-97e1-98b2cd2f5098")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 200.66 113.03) (xy 203.2 113.03)
|
(xy 200.66 113.03) (xy 203.2 113.03)
|
||||||
@ -3582,6 +3965,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "174186dd-383b-40e2-a98a-27eb33a59b32")
|
(uuid "174186dd-383b-40e2-a98a-27eb33a59b32")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 40.64 26.67) (xy 40.64 24.13)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "17b24c28-e602-48eb-92c5-1138973e1139")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 90.17 99.06) (xy 90.17 130.81)
|
(xy 90.17 99.06) (xy 90.17 130.81)
|
||||||
@ -3612,6 +4005,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "1dd93b3c-9798-4dba-aa76-7cfe2eaf8f5d")
|
(uuid "1dd93b3c-9798-4dba-aa76-7cfe2eaf8f5d")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 120.65 45.72) (xy 120.65 20.32)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "1e1df1b6-d2c1-493e-96f2-175cb587f0af")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 140.97 53.34) (xy 118.11 53.34)
|
(xy 140.97 53.34) (xy 118.11 53.34)
|
||||||
@ -3742,6 +4145,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "276ac9ec-ca3f-4ccc-970e-5137f354a728")
|
(uuid "276ac9ec-ca3f-4ccc-970e-5137f354a728")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 90.17 24.13) (xy 90.17 43.18)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "27e51e00-246f-4787-a420-019cbc1e374a")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 205.74 74.93) (xy 210.82 74.93)
|
(xy 205.74 74.93) (xy 210.82 74.93)
|
||||||
@ -3962,6 +4375,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "443508a4-1ea7-4f0b-8576-df4cdb389e90")
|
(uuid "443508a4-1ea7-4f0b-8576-df4cdb389e90")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 74.93 26.67) (xy 92.71 26.67)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "45fb3eb4-f681-4981-8480-6b69b0458b67")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 63.5 161.29) (xy 76.2 161.29)
|
(xy 63.5 161.29) (xy 76.2 161.29)
|
||||||
@ -3972,6 +4395,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "47125bf3-20b3-4f69-a167-7f586e4f0e51")
|
(uuid "47125bf3-20b3-4f69-a167-7f586e4f0e51")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 120.65 20.32) (xy 40.64 20.32)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "47c1de4d-563b-4827-83f9-db72ace93f3a")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 43.18 71.12) (xy 41.91 71.12)
|
(xy 43.18 71.12) (xy 41.91 71.12)
|
||||||
@ -4052,6 +4485,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "4ec8ca24-dd9d-4444-9c35-18f203382609")
|
(uuid "4ec8ca24-dd9d-4444-9c35-18f203382609")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 43.18 26.67) (xy 40.64 26.67)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "4f9d939c-88c4-464e-85a9-1c941ed6ab37")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 55.88 78.74) (xy 67.31 78.74)
|
(xy 55.88 78.74) (xy 67.31 78.74)
|
||||||
@ -4062,6 +4505,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "511008af-9282-402b-914a-c6d0e45387fa")
|
(uuid "511008af-9282-402b-914a-c6d0e45387fa")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 40.64 24.13) (xy 43.18 24.13)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "5178c3ef-e7a2-4516-90c1-188bd509cadf")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 92.71 66.04) (xy 140.97 66.04)
|
(xy 92.71 66.04) (xy 140.97 66.04)
|
||||||
@ -4192,6 +4645,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "687ef4c8-f0d0-482a-a38e-d4472923058e")
|
(uuid "687ef4c8-f0d0-482a-a38e-d4472923058e")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 74.93 24.13) (xy 90.17 24.13)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "69b13b44-605b-44a5-98ba-5b4ca660ef87")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 63.5 151.13) (xy 76.2 151.13)
|
(xy 63.5 151.13) (xy 76.2 151.13)
|
||||||
@ -4442,6 +4905,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "888fcbff-8854-48b0-9ea0-16b8929051e1")
|
(uuid "888fcbff-8854-48b0-9ea0-16b8929051e1")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 40.64 20.32) (xy 40.64 24.13)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "88ee4bdf-c741-469c-b3cd-492b156f9b29")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 138.43 40.64) (xy 140.97 40.64)
|
(xy 138.43 40.64) (xy 140.97 40.64)
|
||||||
@ -4862,6 +5335,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "b290186a-46fd-4ff2-8061-aa12fcd561b2")
|
(uuid "b290186a-46fd-4ff2-8061-aa12fcd561b2")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 55.88 26.67) (xy 67.31 26.67)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "b2c5b7ec-5c1d-4af8-8531-58046a6f247c")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 234.95 113.03) (xy 228.6 113.03)
|
(xy 234.95 113.03) (xy 228.6 113.03)
|
||||||
@ -5052,6 +5535,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "c29ec434-c89d-4a73-8bf8-3ec9c475503b")
|
(uuid "c29ec434-c89d-4a73-8bf8-3ec9c475503b")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 140.97 45.72) (xy 120.65 45.72)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "c2d5abd0-e0ca-4aa4-92bb-caa693aa3cc4")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 83.82 140.97) (xy 100.33 140.97)
|
(xy 83.82 140.97) (xy 100.33 140.97)
|
||||||
@ -5182,6 +5675,16 @@
|
|||||||
)
|
)
|
||||||
(uuid "d013716e-a931-481c-89e5-cc1c74001c93")
|
(uuid "d013716e-a931-481c-89e5-cc1c74001c93")
|
||||||
)
|
)
|
||||||
|
(wire
|
||||||
|
(pts
|
||||||
|
(xy 92.71 26.67) (xy 92.71 45.72)
|
||||||
|
)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(uuid "d2486cf2-e14e-4edf-a1f2-fc0dfcd6bfd3")
|
||||||
|
)
|
||||||
(wire
|
(wire
|
||||||
(pts
|
(pts
|
||||||
(xy 205.74 40.64) (xy 205.74 52.07)
|
(xy 205.74 40.64) (xy 205.74 52.07)
|
||||||
@ -5482,119 +5985,8 @@
|
|||||||
)
|
)
|
||||||
(uuid "fef1ec51-1355-49b9-9ba4-ac3e3fc7562e")
|
(uuid "fef1ec51-1355-49b9-9ba4-ac3e3fc7562e")
|
||||||
)
|
)
|
||||||
(text "Gimbal Y2 (Right)"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 223.52 76.2 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify left bottom)
|
|
||||||
)
|
|
||||||
(uuid "0666d517-3399-4e5a-a88e-28cb046bab53")
|
|
||||||
)
|
|
||||||
(text "Right \nHat Switch\n20-24"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 50.8 160.02 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify right bottom)
|
|
||||||
)
|
|
||||||
(uuid "28c6deef-0288-400d-b950-adf2dba1adf7")
|
|
||||||
)
|
|
||||||
(text "ELRS TX\n"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 254 132.08 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify left bottom)
|
|
||||||
)
|
|
||||||
(uuid "666e68ae-3562-4b09-b825-c9f5445176d4")
|
|
||||||
)
|
|
||||||
(text "Right\nTop\nButtons\n10-14\n"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 38.1 110.49 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify right bottom)
|
|
||||||
)
|
|
||||||
(uuid "76352af5-c5ca-48fd-bebd-b8161cb04ff3")
|
|
||||||
)
|
|
||||||
(text "Left \nHat Switch\n15-19"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 50.8 139.7 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify right bottom)
|
|
||||||
)
|
|
||||||
(uuid "921cb360-d960-4007-bfc3-e8983d17359f")
|
|
||||||
)
|
|
||||||
(text "Gimbal X2 (Right)"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 223.52 64.77 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify left bottom)
|
|
||||||
)
|
|
||||||
(uuid "9ef99c88-97e7-430c-98f1-8b504d8de253")
|
|
||||||
)
|
|
||||||
(text "Left\nTop\nButtons\n5-9"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 36.83 82.55 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify right bottom)
|
|
||||||
)
|
|
||||||
(uuid "9febdcbd-7a0f-4f31-b915-6903ca937f26")
|
|
||||||
)
|
|
||||||
(text "Bottom\nChassis\nButtons\n0-4"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 36.83 54.61 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify right bottom)
|
|
||||||
)
|
|
||||||
(uuid "c7b70dbb-f435-4a57-aa9c-8cf50da328cb")
|
|
||||||
)
|
|
||||||
(text "Gimbal X1 (Left)"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 223.52 41.91 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify left bottom)
|
|
||||||
)
|
|
||||||
(uuid "d87b3a29-cc14-4431-b108-be87ff886dfb")
|
|
||||||
)
|
|
||||||
(text "Gimbal Y1 (Left)"
|
|
||||||
(exclude_from_sim no)
|
|
||||||
(at 223.52 53.34 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 2.27 2.27)
|
|
||||||
)
|
|
||||||
(justify left bottom)
|
|
||||||
)
|
|
||||||
(uuid "ed3a0309-c84e-411e-aaa8-eac48814b746")
|
|
||||||
)
|
|
||||||
(label "COL_B"
|
(label "COL_B"
|
||||||
(at 102.87 66.04 0)
|
(at 102.87 66.04 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5605,7 +5997,6 @@
|
|||||||
)
|
)
|
||||||
(label "ROW_E"
|
(label "ROW_E"
|
||||||
(at 102.87 148.59 0)
|
(at 102.87 148.59 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5616,7 +6007,6 @@
|
|||||||
)
|
)
|
||||||
(label "14"
|
(label "14"
|
||||||
(at 57.15 109.22 0)
|
(at 57.15 109.22 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5627,7 +6017,6 @@
|
|||||||
)
|
)
|
||||||
(label "20"
|
(label "20"
|
||||||
(at 64.77 151.13 0)
|
(at 64.77 151.13 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5638,7 +6027,6 @@
|
|||||||
)
|
)
|
||||||
(label "13"
|
(label "13"
|
||||||
(at 57.15 106.68 0)
|
(at 57.15 106.68 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5649,7 +6037,6 @@
|
|||||||
)
|
)
|
||||||
(label "ROW_A"
|
(label "ROW_A"
|
||||||
(at 102.87 33.02 0)
|
(at 102.87 33.02 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5660,7 +6047,6 @@
|
|||||||
)
|
)
|
||||||
(label "5"
|
(label "5"
|
||||||
(at 57.15 71.12 0)
|
(at 57.15 71.12 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5671,7 +6057,6 @@
|
|||||||
)
|
)
|
||||||
(label "12"
|
(label "12"
|
||||||
(at 57.15 104.14 0)
|
(at 57.15 104.14 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5682,7 +6067,6 @@
|
|||||||
)
|
)
|
||||||
(label "ROW_C"
|
(label "ROW_C"
|
||||||
(at 102.87 88.9 0)
|
(at 102.87 88.9 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5691,9 +6075,18 @@
|
|||||||
)
|
)
|
||||||
(uuid "42ca3c1b-bbde-4182-bffb-22b88412990a")
|
(uuid "42ca3c1b-bbde-4182-bffb-22b88412990a")
|
||||||
)
|
)
|
||||||
|
(label "25"
|
||||||
|
(at 57.15 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "53f48e32-a548-4738-916b-949c45c9ba6a")
|
||||||
|
)
|
||||||
(label "SCL"
|
(label "SCL"
|
||||||
(at 168.91 71.12 0)
|
(at 168.91 71.12 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5704,7 +6097,6 @@
|
|||||||
)
|
)
|
||||||
(label "ROW_B"
|
(label "ROW_B"
|
||||||
(at 102.87 58.42 0)
|
(at 102.87 58.42 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5715,7 +6107,6 @@
|
|||||||
)
|
)
|
||||||
(label "COL_E"
|
(label "COL_E"
|
||||||
(at 102.87 73.66 0)
|
(at 102.87 73.66 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5726,7 +6117,6 @@
|
|||||||
)
|
)
|
||||||
(label "16"
|
(label "16"
|
||||||
(at 64.77 133.35 0)
|
(at 64.77 133.35 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5737,7 +6127,6 @@
|
|||||||
)
|
)
|
||||||
(label "COL_D"
|
(label "COL_D"
|
||||||
(at 102.87 71.12 0)
|
(at 102.87 71.12 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5748,7 +6137,6 @@
|
|||||||
)
|
)
|
||||||
(label "1"
|
(label "1"
|
||||||
(at 57.15 45.72 0)
|
(at 57.15 45.72 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5759,7 +6147,6 @@
|
|||||||
)
|
)
|
||||||
(label "24"
|
(label "24"
|
||||||
(at 64.77 161.29 0)
|
(at 64.77 161.29 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5770,7 +6157,6 @@
|
|||||||
)
|
)
|
||||||
(label "6"
|
(label "6"
|
||||||
(at 57.15 73.66 0)
|
(at 57.15 73.66 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5781,7 +6167,6 @@
|
|||||||
)
|
)
|
||||||
(label "2"
|
(label "2"
|
||||||
(at 57.15 48.26 0)
|
(at 57.15 48.26 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5792,7 +6177,6 @@
|
|||||||
)
|
)
|
||||||
(label "17"
|
(label "17"
|
||||||
(at 64.77 135.89 0)
|
(at 64.77 135.89 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5803,7 +6187,6 @@
|
|||||||
)
|
)
|
||||||
(label "9"
|
(label "9"
|
||||||
(at 57.15 81.28 0)
|
(at 57.15 81.28 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5814,7 +6197,6 @@
|
|||||||
)
|
)
|
||||||
(label "COL_C"
|
(label "COL_C"
|
||||||
(at 102.87 68.58 0)
|
(at 102.87 68.58 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5825,7 +6207,6 @@
|
|||||||
)
|
)
|
||||||
(label "3"
|
(label "3"
|
||||||
(at 57.15 50.8 0)
|
(at 57.15 50.8 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5836,7 +6217,6 @@
|
|||||||
)
|
)
|
||||||
(label "15"
|
(label "15"
|
||||||
(at 64.77 130.81 0)
|
(at 64.77 130.81 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5845,9 +6225,18 @@
|
|||||||
)
|
)
|
||||||
(uuid "a62949b1-4474-4ed4-8759-09439e5b4e57")
|
(uuid "a62949b1-4474-4ed4-8759-09439e5b4e57")
|
||||||
)
|
)
|
||||||
|
(label "ROW_F"
|
||||||
|
(at 102.87 20.32 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "a9e223f9-ffb2-4c91-a5be-434b50fffb87")
|
||||||
|
)
|
||||||
(label "21"
|
(label "21"
|
||||||
(at 64.77 153.67 0)
|
(at 64.77 153.67 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5858,7 +6247,6 @@
|
|||||||
)
|
)
|
||||||
(label "ROW_D"
|
(label "ROW_D"
|
||||||
(at 102.87 128.27 0)
|
(at 102.87 128.27 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5869,7 +6257,6 @@
|
|||||||
)
|
)
|
||||||
(label "23"
|
(label "23"
|
||||||
(at 64.77 158.75 0)
|
(at 64.77 158.75 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5880,7 +6267,6 @@
|
|||||||
)
|
)
|
||||||
(label "18"
|
(label "18"
|
||||||
(at 64.77 138.43 0)
|
(at 64.77 138.43 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5889,9 +6275,18 @@
|
|||||||
)
|
)
|
||||||
(uuid "bb843c91-2a51-41f1-b9b5-35c508fa8c2b")
|
(uuid "bb843c91-2a51-41f1-b9b5-35c508fa8c2b")
|
||||||
)
|
)
|
||||||
|
(label "26"
|
||||||
|
(at 57.15 26.67 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(justify left bottom)
|
||||||
|
)
|
||||||
|
(uuid "bfad85d3-6ad3-4abb-a160-37a46102177f")
|
||||||
|
)
|
||||||
(label "4"
|
(label "4"
|
||||||
(at 57.15 53.34 0)
|
(at 57.15 53.34 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5902,7 +6297,6 @@
|
|||||||
)
|
)
|
||||||
(label "19"
|
(label "19"
|
||||||
(at 64.77 140.97 0)
|
(at 64.77 140.97 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5913,7 +6307,6 @@
|
|||||||
)
|
)
|
||||||
(label "7"
|
(label "7"
|
||||||
(at 57.15 76.2 0)
|
(at 57.15 76.2 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5924,7 +6317,6 @@
|
|||||||
)
|
)
|
||||||
(label "COL_A"
|
(label "COL_A"
|
||||||
(at 102.87 63.5 0)
|
(at 102.87 63.5 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5935,7 +6327,6 @@
|
|||||||
)
|
)
|
||||||
(label "SDA"
|
(label "SDA"
|
||||||
(at 168.91 73.66 0)
|
(at 168.91 73.66 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5946,7 +6337,6 @@
|
|||||||
)
|
)
|
||||||
(label "11"
|
(label "11"
|
||||||
(at 57.15 101.6 0)
|
(at 57.15 101.6 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5957,7 +6347,6 @@
|
|||||||
)
|
)
|
||||||
(label "10"
|
(label "10"
|
||||||
(at 57.15 99.06 0)
|
(at 57.15 99.06 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5968,7 +6357,6 @@
|
|||||||
)
|
)
|
||||||
(label "22"
|
(label "22"
|
||||||
(at 64.77 156.21 0)
|
(at 64.77 156.21 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5979,7 +6367,6 @@
|
|||||||
)
|
)
|
||||||
(label "8"
|
(label "8"
|
||||||
(at 57.15 78.74 0)
|
(at 57.15 78.74 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -5990,7 +6377,6 @@
|
|||||||
)
|
)
|
||||||
(label "0"
|
(label "0"
|
||||||
(at 57.15 43.18 0)
|
(at 57.15 43.18 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -7034,6 +7420,92 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(symbol
|
||||||
|
(lib_id "Diode:1N4148W")
|
||||||
|
(at 71.12 26.67 180)
|
||||||
|
(unit 1)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(dnp no)
|
||||||
|
(uuid "46ecf311-7740-4812-ac20-812de8d3e4cf")
|
||||||
|
(property "Reference" "D27"
|
||||||
|
(at 67.31 25.4 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "1N4148W"
|
||||||
|
(at 71.12 21.59 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" "Diode_SMD:D_SOD-123"
|
||||||
|
(at 71.12 22.225 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "https://www.vishay.com/docs/85748/1n4148w.pdf"
|
||||||
|
(at 71.12 26.67 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 71.12 26.67 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Sim.Device" "D"
|
||||||
|
(at 71.12 21.59 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Sim.Pins" "1=K 2=A"
|
||||||
|
(at 71.12 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin "1"
|
||||||
|
(uuid "e0d45ce7-ada9-49c5-9121-632d5025127a")
|
||||||
|
)
|
||||||
|
(pin "2"
|
||||||
|
(uuid "2137e308-87bc-4171-bb81-2eb2925ceae1")
|
||||||
|
)
|
||||||
|
(instances
|
||||||
|
(project "cmdr-joystick"
|
||||||
|
(path "/5b501981-46e2-4084-afad-38073ca78ebd"
|
||||||
|
(reference "D27")
|
||||||
|
(unit 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(symbol
|
(symbol
|
||||||
(lib_id "Diode:1N4148W")
|
(lib_id "Diode:1N4148W")
|
||||||
(at 71.12 76.2 180)
|
(at 71.12 76.2 180)
|
||||||
@ -9550,6 +10022,91 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(symbol
|
||||||
|
(lib_id "Diode:1N4148W")
|
||||||
|
(at 71.12 24.13 180)
|
||||||
|
(unit 1)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(dnp no)
|
||||||
|
(uuid "b7fe8caf-41aa-4e02-a5d9-1eea6f8f65a1")
|
||||||
|
(property "Reference" "D26"
|
||||||
|
(at 67.31 22.86 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "1N4148W"
|
||||||
|
(at 71.12 19.05 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" "Diode_SMD:D_SOD-123"
|
||||||
|
(at 71.12 19.685 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "https://www.vishay.com/docs/85748/1n4148w.pdf"
|
||||||
|
(at 71.12 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 71.12 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Sim.Device" "D"
|
||||||
|
(at 71.12 19.05 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Sim.Pins" "1=K 2=A"
|
||||||
|
(at 71.12 21.59 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin "1"
|
||||||
|
(uuid "3247e64c-a674-403e-b9ca-9f5a27772c9a")
|
||||||
|
)
|
||||||
|
(pin "2"
|
||||||
|
(uuid "1763e96e-860d-4f23-9a5a-9b2434fb5056")
|
||||||
|
)
|
||||||
|
(instances
|
||||||
|
(project "cmdr-joystick"
|
||||||
|
(path "/5b501981-46e2-4084-afad-38073ca78ebd"
|
||||||
|
(reference "D26")
|
||||||
|
(unit 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(symbol
|
(symbol
|
||||||
(lib_id "power:+3.3V")
|
(lib_id "power:+3.3V")
|
||||||
(at 157.48 26.67 0)
|
(at 157.48 26.67 0)
|
||||||
@ -10641,6 +11198,81 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(symbol
|
||||||
|
(lib_id "Connector_Generic:Conn_02x02_Odd_Even")
|
||||||
|
(at 48.26 24.13 0)
|
||||||
|
(unit 1)
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(dnp no)
|
||||||
|
(fields_autoplaced yes)
|
||||||
|
(uuid "f38f8351-71f5-4607-80f0-a42807da220c")
|
||||||
|
(property "Reference" "J11"
|
||||||
|
(at 49.53 17.78 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "Conn_02x02_Odd_Even"
|
||||||
|
(at 49.53 20.32 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" "Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Vertical"
|
||||||
|
(at 48.26 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "~"
|
||||||
|
(at 48.26 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" "Generic connector, double row, 02x02, odd/even pin numbering scheme (row 1 odd numbers, row 2 even numbers), script generated (kicad-library-utils/schlib/autogen/connector/)"
|
||||||
|
(at 48.26 24.13 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin "1"
|
||||||
|
(uuid "dff31445-6af1-4699-a9fd-126f8275a9e8")
|
||||||
|
)
|
||||||
|
(pin "4"
|
||||||
|
(uuid "6eb5391f-f8ad-422f-ada0-ea6481f1e331")
|
||||||
|
)
|
||||||
|
(pin "3"
|
||||||
|
(uuid "c534bd37-80f3-4d24-9a85-f5a058c29572")
|
||||||
|
)
|
||||||
|
(pin "2"
|
||||||
|
(uuid "6555abe8-115d-4871-8cf0-932ecb7a2675")
|
||||||
|
)
|
||||||
|
(instances
|
||||||
|
(project ""
|
||||||
|
(path "/5b501981-46e2-4084-afad-38073ca78ebd"
|
||||||
|
(reference "J11")
|
||||||
|
(unit 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(symbol
|
(symbol
|
||||||
(lib_id "Diode:1N4148W")
|
(lib_id "Diode:1N4148W")
|
||||||
(at 71.12 48.26 180)
|
(at 71.12 48.26 180)
|
||||||
@ -10930,4 +11562,5 @@
|
|||||||
(page "1")
|
(page "1")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
)
|
)
|
||||||
|
|||||||
1
eCAD/cmdr-joystick/fabrication-toolkit-options.json
Normal file
1
eCAD/cmdr-joystick/fabrication-toolkit-options.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"ARCHIVE_NAME": "CMDR_keyboard_rev_b", "EXTRA_LAYERS": "", "ALL_ACTIVE_LAYERS": false, "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false}
|
||||||
BIN
eCAD/cmdr-joystick/production/CMDR_keyboard_rev_b.zip
Normal file
BIN
eCAD/cmdr-joystick/production/CMDR_keyboard_rev_b.zip
Normal file
Binary file not shown.
544
install.sh
544
install.sh
@ -1,544 +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 Joystick 25 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
|
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cmdr-joystick-25"
|
name = "cmdr-joystick"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
# Firmware crate for the CMDR Joystick 25 (RP2040)
|
# Firmware crate for the CMDR Joystick (RP2040)
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core embedded + RP2040 HAL stack (aligned with rp2040-hal v0.11)
|
# Core embedded + RP2040 HAL stack (aligned with rp2040-hal v0.11)
|
||||||
@ -65,7 +65,7 @@ overflow-checks = false
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cmdr-joystick-25"
|
name = "cmdr-joystick"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
bench = false
|
bench = false
|
||||||
test = false
|
test = false
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! Axis processing for CMDR Joystick 25
|
//! Axis processing for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Responsibilities
|
//! Responsibilities
|
||||||
//! - Apply gimbal mode compensation (M10/M7) to raw ADC readings
|
//! - Apply gimbal mode compensation (M10/M7) to raw ADC readings
|
||||||
@ -54,6 +54,7 @@ pub struct GimbalAxis {
|
|||||||
|
|
||||||
impl Default for GimbalAxis {
|
impl Default for GimbalAxis {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Apply new calibration limits supplied by the calibration manager.
|
||||||
GimbalAxis {
|
GimbalAxis {
|
||||||
value: AXIS_CENTER,
|
value: AXIS_CENTER,
|
||||||
value_before_hold: AXIS_CENTER,
|
value_before_hold: AXIS_CENTER,
|
||||||
@ -143,6 +144,7 @@ pub struct VirtualAxis {
|
|||||||
|
|
||||||
impl Default for VirtualAxis {
|
impl Default for VirtualAxis {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Create a virtual axis starting at center with the supplied step size.
|
||||||
VirtualAxis {
|
VirtualAxis {
|
||||||
value: AXIS_CENTER,
|
value: AXIS_CENTER,
|
||||||
step: 5,
|
step: 5,
|
||||||
@ -206,6 +208,7 @@ pub struct AxisManager {
|
|||||||
|
|
||||||
impl Default for AxisManager {
|
impl Default for AxisManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Delegate to `new` so the default stays aligned with explicit constructor logic.
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,6 +465,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_axis_default() {
|
fn test_gimbal_axis_default() {
|
||||||
|
// Factory defaults should leave every axis field pointing at the center span.
|
||||||
let axis = GimbalAxis::default();
|
let axis = GimbalAxis::default();
|
||||||
assert_eq!(axis.value, AXIS_CENTER);
|
assert_eq!(axis.value, AXIS_CENTER);
|
||||||
assert_eq!(axis.min, ADC_MIN);
|
assert_eq!(axis.min, ADC_MIN);
|
||||||
@ -473,6 +477,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_axis_new() {
|
fn test_gimbal_axis_new() {
|
||||||
|
// `new` should be a thin wrapper over `Default` without changing members.
|
||||||
let axis = GimbalAxis::new();
|
let axis = GimbalAxis::new();
|
||||||
assert_eq!(axis.value, AXIS_CENTER);
|
assert_eq!(axis.value, AXIS_CENTER);
|
||||||
assert_eq!(axis.min, ADC_MIN);
|
assert_eq!(axis.min, ADC_MIN);
|
||||||
@ -483,6 +488,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_axis_with_calibration() {
|
fn test_gimbal_axis_with_calibration() {
|
||||||
|
// Axis constructed with explicit calibration should adopt all provided bounds.
|
||||||
let axis = GimbalAxis::with_calibration(100, 3900, 2000);
|
let axis = GimbalAxis::with_calibration(100, 3900, 2000);
|
||||||
assert_eq!(axis.min, 100);
|
assert_eq!(axis.min, 100);
|
||||||
assert_eq!(axis.max, 3900);
|
assert_eq!(axis.max, 3900);
|
||||||
@ -491,6 +497,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_axis_activity_detection() {
|
fn test_gimbal_axis_activity_detection() {
|
||||||
|
// Activity flag should only flip when the axis value actually changes.
|
||||||
let mut axis = GimbalAxis::new();
|
let mut axis = GimbalAxis::new();
|
||||||
|
|
||||||
// Initially no activity (same as previous)
|
// Initially no activity (same as previous)
|
||||||
@ -506,6 +513,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_axis_throttle_hold_processing() {
|
fn test_gimbal_axis_throttle_hold_processing() {
|
||||||
|
// Throttle hold remapping should cover remap, center latch, and pending hold cases.
|
||||||
let mut axis = GimbalAxis::new();
|
let mut axis = GimbalAxis::new();
|
||||||
axis.set_hold(1500); // Set hold value below center
|
axis.set_hold(1500); // Set hold value below center
|
||||||
|
|
||||||
@ -541,6 +549,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_axis_default() {
|
fn test_virtual_axis_default() {
|
||||||
|
// Virtual axis should boot at center with the configured step size.
|
||||||
let virtual_axis = VirtualAxis::default();
|
let virtual_axis = VirtualAxis::default();
|
||||||
assert_eq!(virtual_axis.value, AXIS_CENTER);
|
assert_eq!(virtual_axis.value, AXIS_CENTER);
|
||||||
assert_eq!(virtual_axis.step, 5);
|
assert_eq!(virtual_axis.step, 5);
|
||||||
@ -548,6 +557,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_axis_movement_up() {
|
fn test_virtual_axis_movement_up() {
|
||||||
|
// An upward press should increment the virtual axis and flag activity.
|
||||||
let mut virtual_axis = VirtualAxis::new(10);
|
let mut virtual_axis = VirtualAxis::new(10);
|
||||||
|
|
||||||
// Test upward movement
|
// Test upward movement
|
||||||
@ -558,6 +568,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_axis_movement_down() {
|
fn test_virtual_axis_movement_down() {
|
||||||
|
// A downward press should decrement the virtual axis and flag activity.
|
||||||
let mut virtual_axis = VirtualAxis::new(10);
|
let mut virtual_axis = VirtualAxis::new(10);
|
||||||
|
|
||||||
// Test downward movement
|
// Test downward movement
|
||||||
@ -568,6 +579,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_axis_return_to_center() {
|
fn test_virtual_axis_return_to_center() {
|
||||||
|
// Without inputs the virtual axis should ease back toward the center value.
|
||||||
let mut virtual_axis = VirtualAxis::new(10);
|
let mut virtual_axis = VirtualAxis::new(10);
|
||||||
virtual_axis.value = AXIS_CENTER + 20;
|
virtual_axis.value = AXIS_CENTER + 20;
|
||||||
|
|
||||||
@ -579,6 +591,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_axis_direction_compensation() {
|
fn test_virtual_axis_direction_compensation() {
|
||||||
|
// Reversing direction should recenter before stepping to avoid large jumps.
|
||||||
let mut virtual_axis = VirtualAxis::new(10);
|
let mut virtual_axis = VirtualAxis::new(10);
|
||||||
virtual_axis.value = AXIS_CENTER - 100;
|
virtual_axis.value = AXIS_CENTER - 100;
|
||||||
|
|
||||||
@ -591,6 +604,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_axis_manager_creation() {
|
fn test_axis_manager_creation() {
|
||||||
|
// Manager construction should initialize all axes and state to defaults.
|
||||||
let manager = AxisManager::new();
|
let manager = AxisManager::new();
|
||||||
assert_eq!(manager.axes.len(), NBR_OF_GIMBAL_AXIS);
|
assert_eq!(manager.axes.len(), NBR_OF_GIMBAL_AXIS);
|
||||||
assert_eq!(manager.gimbal_mode, GIMBAL_MODE_M10);
|
assert_eq!(manager.gimbal_mode, GIMBAL_MODE_M10);
|
||||||
@ -600,6 +614,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_compensation_m10() {
|
fn test_gimbal_compensation_m10() {
|
||||||
|
// M10 gimbal compensation must invert the correct pair of axes.
|
||||||
let manager = AxisManager::new(); // Default is M10
|
let manager = AxisManager::new(); // Default is M10
|
||||||
let mut raw_values = [1000, 1500, 2000, 2500];
|
let mut raw_values = [1000, 1500, 2000, 2500];
|
||||||
|
|
||||||
@ -614,6 +629,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_compensation_m7() {
|
fn test_gimbal_compensation_m7() {
|
||||||
|
// M7 gimbal compensation must invert the complementary axes.
|
||||||
let mut manager = AxisManager::new();
|
let mut manager = AxisManager::new();
|
||||||
manager.set_gimbal_mode(GIMBAL_MODE_M7);
|
manager.set_gimbal_mode(GIMBAL_MODE_M7);
|
||||||
let mut raw_values = [1000, 1500, 2000, 2500];
|
let mut raw_values = [1000, 1500, 2000, 2500];
|
||||||
@ -629,6 +645,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_axis_activity_detection() {
|
fn test_axis_activity_detection() {
|
||||||
|
// Manager activity flag should reflect when any axis value changes.
|
||||||
let mut manager = AxisManager::new();
|
let mut manager = AxisManager::new();
|
||||||
|
|
||||||
// No activity initially
|
// No activity initially
|
||||||
@ -644,6 +661,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_axis_value_boundaries() {
|
fn test_calculate_axis_value_boundaries() {
|
||||||
|
// Axis conversion should clamp inputs below min or above max calibration values.
|
||||||
let expo_lut = ExpoLUT::new(0.0); // No expo for testing
|
let expo_lut = ExpoLUT::new(0.0); // No expo for testing
|
||||||
|
|
||||||
// Test min boundary
|
// Test min boundary
|
||||||
@ -657,6 +675,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_axis_value_deadzone() {
|
fn test_calculate_axis_value_deadzone() {
|
||||||
|
// Inputs inside the center deadzone should resolve to the exact center value.
|
||||||
let expo_lut = ExpoLUT::new(0.0); // No expo for testing
|
let expo_lut = ExpoLUT::new(0.0); // No expo for testing
|
||||||
|
|
||||||
// Test center deadzone
|
// Test center deadzone
|
||||||
@ -666,6 +685,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_axis_value_degenerate_calibration() {
|
fn test_calculate_axis_value_degenerate_calibration() {
|
||||||
|
// Degenerate calibration inputs should return the provided center without panicking.
|
||||||
let expo_lut = ExpoLUT::new(0.0);
|
let expo_lut = ExpoLUT::new(0.0);
|
||||||
|
|
||||||
// When calibration collapses to a single point (min=max=center),
|
// When calibration collapses to a single point (min=max=center),
|
||||||
|
|||||||
213
rp2040/src/board.rs
Normal file
213
rp2040/src/board.rs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
//! Board bring-up and peripheral wiring for the CMDR Joystick firmware.
|
||||||
|
|
||||||
|
use crate::button_matrix::{ButtonMatrix, MatrixPins};
|
||||||
|
use crate::hardware::{self, BoardPins};
|
||||||
|
use crate::status::StatusLed;
|
||||||
|
use cortex_m::delay::Delay;
|
||||||
|
use cortex_m::interrupt;
|
||||||
|
use eeprom24x::{addr_size, page_size, unique_serial, Eeprom24x};
|
||||||
|
use rp2040_hal::adc::{Adc, AdcPin};
|
||||||
|
use rp2040_hal::clocks::Clock;
|
||||||
|
use rp2040_hal::gpio::{self, Pin, PullNone};
|
||||||
|
use rp2040_hal::i2c::I2C;
|
||||||
|
use rp2040_hal::pac;
|
||||||
|
use rp2040_hal::pio::PIOExt;
|
||||||
|
use rp2040_hal::sio::Sio;
|
||||||
|
use rp2040_hal::timer::Timer;
|
||||||
|
use rp2040_hal::watchdog::Watchdog;
|
||||||
|
use rp2040_hal::{clocks::init_clocks_and_plls, gpio::FunctionSioInput};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
use usb_device::class_prelude::UsbBusAllocator;
|
||||||
|
|
||||||
|
pub type JoystickMatrix = ButtonMatrix<
|
||||||
|
MatrixPins<{ hardware::BUTTON_ROWS }, { hardware::BUTTON_COLS }>,
|
||||||
|
{ hardware::BUTTON_ROWS },
|
||||||
|
{ hardware::BUTTON_COLS },
|
||||||
|
{ hardware::NUMBER_OF_BUTTONS },
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub type JoystickStatusLed = StatusLed<pac::PIO0, rp2040_hal::pio::SM0, hardware::StatusLedPin>;
|
||||||
|
|
||||||
|
type BoardI2c = I2C<pac::I2C1, (hardware::I2cSdaPin, hardware::I2cSclPin)>;
|
||||||
|
type BoardEeprom = Eeprom24x<BoardI2c, page_size::B32, addr_size::TwoBytes, unique_serial::No>;
|
||||||
|
|
||||||
|
/// Strongly-typed collection of ADC-capable pins for each physical gimbal axis.
|
||||||
|
pub struct AxisAnalogPins {
|
||||||
|
pub left_x: AdcPin<Pin<gpio::bank0::Gpio29, FunctionSioInput, PullNone>>,
|
||||||
|
pub left_y: AdcPin<Pin<gpio::bank0::Gpio28, FunctionSioInput, PullNone>>,
|
||||||
|
pub right_x: AdcPin<Pin<gpio::bank0::Gpio27, FunctionSioInput, PullNone>>,
|
||||||
|
pub right_y: AdcPin<Pin<gpio::bank0::Gpio26, FunctionSioInput, PullNone>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AxisAnalogPins {
|
||||||
|
fn new(inputs: hardware::AxisInputs) -> Self {
|
||||||
|
// Wrap the raw GPIO inputs into ADC-capable pins for each physical axis.
|
||||||
|
let left_x = AdcPin::new(inputs.left_x).unwrap();
|
||||||
|
let left_y = AdcPin::new(inputs.left_y).unwrap();
|
||||||
|
let right_x = AdcPin::new(inputs.right_x).unwrap();
|
||||||
|
let right_y = AdcPin::new(inputs.right_y).unwrap();
|
||||||
|
Self {
|
||||||
|
left_x,
|
||||||
|
left_y,
|
||||||
|
right_x,
|
||||||
|
right_y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aggregates the runtime peripherals used by the joystick firmware.
|
||||||
|
pub struct Board {
|
||||||
|
button_matrix: JoystickMatrix,
|
||||||
|
status_led: JoystickStatusLed,
|
||||||
|
delay: Delay,
|
||||||
|
timer: Timer,
|
||||||
|
adc: Adc,
|
||||||
|
axis_pins: AxisAnalogPins,
|
||||||
|
left_extra_button: hardware::ExtraButtonPin,
|
||||||
|
right_extra_button: hardware::ExtraButtonPin,
|
||||||
|
eeprom: BoardEeprom,
|
||||||
|
usb_bus: &'static UsbBusAllocator<rp2040_hal::usb::UsbBus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Board components handed off to the application after initialization.
|
||||||
|
pub struct BoardParts {
|
||||||
|
pub button_matrix: JoystickMatrix,
|
||||||
|
pub status_led: JoystickStatusLed,
|
||||||
|
pub delay: Delay,
|
||||||
|
pub timer: Timer,
|
||||||
|
pub adc: Adc,
|
||||||
|
pub axis_pins: AxisAnalogPins,
|
||||||
|
pub left_extra_button: hardware::ExtraButtonPin,
|
||||||
|
pub right_extra_button: hardware::ExtraButtonPin,
|
||||||
|
pub eeprom: BoardEeprom,
|
||||||
|
pub usb_bus: &'static UsbBusAllocator<rp2040_hal::usb::UsbBus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// Acquire RP2040 peripheral handles before configuration begins.
|
||||||
|
let mut pac = pac::Peripherals::take().unwrap();
|
||||||
|
let core = pac::CorePeripherals::take().unwrap();
|
||||||
|
|
||||||
|
let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
||||||
|
// Bring up the primary system and USB clocks using the external crystal.
|
||||||
|
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);
|
||||||
|
// Split GPIO banks and translate them into the strongly typed board pins.
|
||||||
|
let raw_pins = gpio::Pins::new(
|
||||||
|
pac.IO_BANK0,
|
||||||
|
pac.PADS_BANK0,
|
||||||
|
sio.gpio_bank0,
|
||||||
|
&mut pac.RESETS,
|
||||||
|
);
|
||||||
|
let pins = BoardPins::new(raw_pins);
|
||||||
|
|
||||||
|
let matrix_pins = MatrixPins::new(pins.matrix_rows, pins.matrix_cols);
|
||||||
|
// Create the button matrix scanner with firmware debounce parameters.
|
||||||
|
let mut button_matrix = ButtonMatrix::new(
|
||||||
|
matrix_pins,
|
||||||
|
hardware::MATRIX_DEBOUNCE_SCANS,
|
||||||
|
hardware::MIN_PRESS_SPACING_SCANS,
|
||||||
|
);
|
||||||
|
button_matrix.init_pins();
|
||||||
|
|
||||||
|
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
|
||||||
|
// Configure the WS2812 status LED using a dedicated PIO state machine.
|
||||||
|
let status_led = StatusLed::new(
|
||||||
|
pins.status_led,
|
||||||
|
&mut pio,
|
||||||
|
sm0,
|
||||||
|
clocks.peripheral_clock.freq(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up timers for scheduling and a blocking delay helper.
|
||||||
|
let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
|
||||||
|
let delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
||||||
|
|
||||||
|
// Build the I²C bus and EEPROM driver used for calibration persistence.
|
||||||
|
let i2c = I2C::i2c1(
|
||||||
|
pac.I2C1,
|
||||||
|
pins.i2c_sda,
|
||||||
|
pins.i2c_scl,
|
||||||
|
hardware::i2c::frequency(),
|
||||||
|
&mut pac.RESETS,
|
||||||
|
hardware::i2c::system_clock(),
|
||||||
|
);
|
||||||
|
let eeprom = Eeprom24x::new_24x32(i2c, hardware::i2c::EEPROM_ADDRESS);
|
||||||
|
|
||||||
|
// Bring up the ADC block and wrap each axis input pin.
|
||||||
|
let adc = Adc::new(pac.ADC, &mut pac.RESETS);
|
||||||
|
let axis_pins = AxisAnalogPins::new(pins.axis_inputs);
|
||||||
|
|
||||||
|
// Prepare a global USB bus allocator for the joystick HID device.
|
||||||
|
let usb_bus = usb_allocator(
|
||||||
|
pac.USBCTRL_REGS,
|
||||||
|
pac.USBCTRL_DPRAM,
|
||||||
|
clocks.usb_clock,
|
||||||
|
&mut pac.RESETS,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
button_matrix,
|
||||||
|
status_led,
|
||||||
|
delay,
|
||||||
|
timer,
|
||||||
|
adc,
|
||||||
|
axis_pins,
|
||||||
|
left_extra_button: pins.left_extra_button,
|
||||||
|
right_extra_button: pins.right_extra_button,
|
||||||
|
eeprom,
|
||||||
|
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,
|
||||||
|
adc: self.adc,
|
||||||
|
axis_pins: self.axis_pins,
|
||||||
|
left_extra_button: self.left_extra_button,
|
||||||
|
right_extra_button: self.right_extra_button,
|
||||||
|
eeprom: self.eeprom,
|
||||||
|
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> {
|
||||||
|
// Lazily create the shared USB bus allocator so HID endpoints can borrow it.
|
||||||
|
static USB_BUS: StaticCell<UsbBusAllocator<rp2040_hal::usb::UsbBus>> = StaticCell::new();
|
||||||
|
|
||||||
|
// Wire up the USB bus allocator, HID class, and joystick endpoint once and reuse it.
|
||||||
|
|
||||||
|
interrupt::free(|_| {
|
||||||
|
USB_BUS.init_with(|| {
|
||||||
|
UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new(
|
||||||
|
usbctrl_regs,
|
||||||
|
usbctrl_dpram,
|
||||||
|
usb_clock,
|
||||||
|
true,
|
||||||
|
resets,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
30
rp2040/src/bootloader.rs
Normal file
30
rp2040/src/bootloader.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//! Bootloader helpers shared between power-on checks and runtime button chords.
|
||||||
|
|
||||||
|
use crate::status::{StatusLed, StatusMode};
|
||||||
|
use cortex_m::asm;
|
||||||
|
use rp2040_hal::gpio::AnyPin;
|
||||||
|
use rp2040_hal::pio::{PIOExt, StateMachineIndex};
|
||||||
|
|
||||||
|
/// Returns `true` when the power-on matrix snapshot requests bootloader entry.
|
||||||
|
///
|
||||||
|
/// The original firmware required the front-left-lower button to be held during
|
||||||
|
/// power-up to jump straight into ROM boot.
|
||||||
|
pub fn startup_requested(buttons: &[bool; crate::hardware::NUMBER_OF_BUTTONS]) -> bool {
|
||||||
|
buttons[crate::mapping::BUTTON_FRONT_LEFT_LOWER]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts the RP2040 into the ROM bootloader after updating the status LED.
|
||||||
|
pub fn enter<P, SM, I>(status_led: &mut StatusLed<P, SM, I>) -> !
|
||||||
|
where
|
||||||
|
P: PIOExt,
|
||||||
|
SM: StateMachineIndex,
|
||||||
|
I: AnyPin<Function = P::PinFunction>,
|
||||||
|
{
|
||||||
|
status_led.update(StatusMode::Bootloader);
|
||||||
|
let gpio_activity_pin_mask: u32 = 0;
|
||||||
|
let disable_interface_mask: u32 = 0;
|
||||||
|
rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask);
|
||||||
|
loop {
|
||||||
|
asm::nop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,225 +1,244 @@
|
|||||||
//! Button matrix scanner for CMDR Joystick 25
|
//! Button matrix scanner for CMDR Joystick.
|
||||||
//!
|
//!
|
||||||
//! Scans a row/column matrix and produces a debounced boolean state for each
|
//! Mirrors the refactor performed for the keyboard firmware: the matrix owns
|
||||||
//! button. Designed for small matrices on microcontrollers where timing is
|
//! concrete pins, exposes a small `MatrixPinAccess` trait, and keeps the
|
||||||
//! deterministic and GPIO is plentiful.
|
//! debouncing + minimum press spacing behaviour identical to the original
|
||||||
//!
|
//! joystick implementation.
|
||||||
//! - Rows are configured as inputs with pull‑ups
|
|
||||||
//! - Columns are configured as push‑pull outputs
|
|
||||||
//! - Debounce is handled per‑button using a simple counter
|
|
||||||
//! - A tiny inter‑column delay is inserted to allow signals to settle
|
|
||||||
|
|
||||||
use core::convert::Infallible;
|
|
||||||
use cortex_m::delay::Delay;
|
use cortex_m::delay::Delay;
|
||||||
use embedded_hal::digital::{InputPin, OutputPin};
|
use embedded_hal::digital::{InputPin, OutputPin};
|
||||||
|
use rp2040_hal::gpio::{DynPinId, FunctionSioInput, FunctionSioOutput, Pin, PullNone, PullUp};
|
||||||
|
|
||||||
/// Button matrix driver
|
/// Abstraction over the matrix pins so the scanner can work with either the
|
||||||
///
|
/// concrete RP2040 pins or test doubles.
|
||||||
/// Generics
|
pub trait MatrixPinAccess<const ROWS: usize, const COLS: usize> {
|
||||||
/// - `R`: number of rows
|
fn init_columns(&mut self);
|
||||||
/// - `C`: number of columns
|
fn set_column_low(&mut self, column: usize);
|
||||||
/// - `N`: total number of buttons (usually `R * C`)
|
fn set_column_high(&mut self, column: usize);
|
||||||
///
|
fn read_row(&mut self, row: usize) -> bool;
|
||||||
/// Example
|
|
||||||
/// ```ignore
|
|
||||||
/// // 4 rows, 6 columns, 24 buttons, 5-scan debounce
|
|
||||||
/// let mut matrix: ButtonMatrix<4, 6, 24> = ButtonMatrix::new(row_pins, col_pins, 5);
|
|
||||||
/// matrix.init_pins();
|
|
||||||
/// loop {
|
|
||||||
/// matrix.scan_matrix(&mut delay);
|
|
||||||
/// let states = matrix.buttons_pressed();
|
|
||||||
/// // use `states`
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub struct ButtonMatrix<'a, const R: usize, const C: usize, const N: usize> {
|
|
||||||
rows: &'a mut [&'a mut dyn InputPin<Error = Infallible>; R],
|
|
||||||
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; C],
|
|
||||||
pressed: [bool; N],
|
|
||||||
debounce: u8,
|
|
||||||
debounce_counter: [u8; N],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> {
|
/// Concrete matrix pins backed by RP2040 GPIO using dynamic pin IDs.
|
||||||
/// Creates a new button matrix.
|
type RowPin = Pin<DynPinId, FunctionSioInput, PullUp>;
|
||||||
///
|
type ColPin = Pin<DynPinId, FunctionSioOutput, PullNone>;
|
||||||
/// Arguments
|
|
||||||
/// - `rows`: array of row pins (inputs with pull‑ups)
|
pub struct MatrixPins<const ROWS: usize, const COLS: usize> {
|
||||||
/// - `cols`: array of column pins (push‑pull outputs)
|
rows: [RowPin; ROWS],
|
||||||
/// - `debounce`: number of consecutive scans a change must persist before it is accepted
|
cols: [ColPin; COLS],
|
||||||
pub fn new(
|
}
|
||||||
rows: &'a mut [&'a mut dyn InputPin<Error = Infallible>; R],
|
|
||||||
cols: &'a mut [&'a mut dyn OutputPin<Error = Infallible>; C],
|
impl<const ROWS: usize, const COLS: usize> MatrixPins<ROWS, COLS> {
|
||||||
debounce: u8,
|
pub fn new(rows: [RowPin; ROWS], cols: [ColPin; COLS]) -> Self {
|
||||||
) -> Self {
|
Self { rows, cols }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const ROWS: usize, const COLS: usize> MatrixPinAccess<ROWS, COLS> for MatrixPins<ROWS, COLS> {
|
||||||
|
fn init_columns(&mut self) {
|
||||||
|
// Default all columns high so rows can be strobed one at a time.
|
||||||
|
for column in self.cols.iter_mut() {
|
||||||
|
let _ = column.set_high();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_column_low(&mut self, column: usize) {
|
||||||
|
// Pull the active column low before scanning its rows.
|
||||||
|
let _ = self.cols[column].set_low();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_column_high(&mut self, column: usize) {
|
||||||
|
// Release the column after scanning so other columns remain idle.
|
||||||
|
let _ = self.cols[column].set_high();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_row(&mut self, row: usize) -> bool {
|
||||||
|
// Treat any low level as a pressed switch, defaulting to false on IO errors.
|
||||||
|
self.rows[row].is_low().unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Row/column scanned button matrix driver with debounce counters and minimum
|
||||||
|
/// spacing between subsequent presses of the same key.
|
||||||
|
pub struct ButtonMatrix<P, const ROWS: usize, const COLS: usize, const BUTTONS: usize>
|
||||||
|
where
|
||||||
|
P: MatrixPinAccess<ROWS, COLS>,
|
||||||
|
{
|
||||||
|
pins: P,
|
||||||
|
pressed: [bool; BUTTONS],
|
||||||
|
debounce_threshold: u8,
|
||||||
|
debounce_counter: [u8; BUTTONS],
|
||||||
|
last_press_scan: [u32; BUTTONS],
|
||||||
|
min_press_gap_scans: u32,
|
||||||
|
scan_counter: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P, const ROWS: usize, const COLS: usize, const BUTTONS: usize>
|
||||||
|
ButtonMatrix<P, ROWS, COLS, BUTTONS>
|
||||||
|
where
|
||||||
|
P: MatrixPinAccess<ROWS, COLS>,
|
||||||
|
{
|
||||||
|
pub fn new(pins: P, debounce_threshold: u8, min_press_gap_scans: u32) -> Self {
|
||||||
|
debug_assert_eq!(BUTTONS, ROWS * COLS);
|
||||||
Self {
|
Self {
|
||||||
rows,
|
pins,
|
||||||
cols,
|
pressed: [false; BUTTONS],
|
||||||
pressed: [false; N],
|
debounce_threshold,
|
||||||
debounce,
|
debounce_counter: [0; BUTTONS],
|
||||||
debounce_counter: [0; N],
|
last_press_scan: [0; BUTTONS],
|
||||||
|
min_press_gap_scans,
|
||||||
|
scan_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the matrix GPIOs (set all columns high).
|
|
||||||
///
|
|
||||||
/// Call once before the first scan.
|
|
||||||
pub fn init_pins(&mut self) {
|
pub fn init_pins(&mut self) {
|
||||||
for col in self.cols.iter_mut() {
|
self.pins.init_columns();
|
||||||
col.set_high().unwrap();
|
}
|
||||||
|
|
||||||
|
pub fn prime(&mut self, delay: &mut Delay, passes: usize) {
|
||||||
|
for _ in 0..passes {
|
||||||
|
self.scan_matrix(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scan the matrix and update each button's debounced state.
|
|
||||||
///
|
|
||||||
/// Call at a fixed cadence. The simple debounce uses a per‑button counter: only
|
|
||||||
/// when a changed level is observed for `debounce` consecutive scans is the
|
|
||||||
/// new state committed.
|
|
||||||
///
|
|
||||||
/// Arguments
|
|
||||||
/// - `delay`: short delay implementation used to let signals settle between columns
|
|
||||||
pub fn scan_matrix(&mut self, delay: &mut Delay) {
|
pub fn scan_matrix(&mut self, delay: &mut Delay) {
|
||||||
for col_index in 0..self.cols.len() {
|
self.scan_counter = self.scan_counter.wrapping_add(1);
|
||||||
self.cols[col_index].set_low().unwrap();
|
for column in 0..COLS {
|
||||||
|
self.pins.set_column_low(column);
|
||||||
delay.delay_us(1);
|
delay.delay_us(1);
|
||||||
self.process_column(col_index);
|
self.process_column(column);
|
||||||
self.cols[col_index].set_high().unwrap();
|
self.pins.set_column_high(column);
|
||||||
delay.delay_us(1);
|
delay.delay_us(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process a single column: drive low, sample rows, update debounce state, then release high.
|
pub fn buttons_pressed(&self) -> [bool; BUTTONS] {
|
||||||
///
|
self.pressed
|
||||||
/// Arguments
|
}
|
||||||
/// - `col_index`: index of the column being scanned
|
|
||||||
fn process_column(&mut self, col_index: usize) {
|
|
||||||
for row_index in 0..self.rows.len() {
|
|
||||||
let button_index: usize = col_index + (row_index * C);
|
|
||||||
let current_state = self.rows[row_index].is_low().unwrap();
|
|
||||||
|
|
||||||
if current_state == self.pressed[button_index] {
|
fn process_column(&mut self, column: usize) {
|
||||||
self.debounce_counter[button_index] = 0;
|
// Drive a single column scan to update button press history.
|
||||||
|
for row in 0..ROWS {
|
||||||
|
let index = column + (row * COLS);
|
||||||
|
let current_state = self.pins.read_row(row);
|
||||||
|
|
||||||
|
if current_state == self.pressed[index] {
|
||||||
|
self.debounce_counter[index] = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.debounce_counter[button_index] += 1;
|
self.debounce_counter[index] = self.debounce_counter[index].saturating_add(1);
|
||||||
if self.debounce_counter[button_index] >= self.debounce {
|
if self.debounce_counter[index] < self.debounce_threshold {
|
||||||
self.pressed[button_index] = current_state;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.debounce_counter[index] = 0;
|
||||||
|
if current_state {
|
||||||
|
if self.should_register_press(index) {
|
||||||
|
self.pressed[index] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.pressed[index] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of the debounced pressed state for all buttons.
|
fn should_register_press(&mut self, index: usize) -> bool {
|
||||||
///
|
// Decide if a press should register given debounce timing.
|
||||||
/// For small `N` this copy is cheap. If needed, the API could be extended to
|
let elapsed = self.scan_counter.wrapping_sub(self.last_press_scan[index]);
|
||||||
/// return a reference in the future.
|
let can_register = self.last_press_scan[index] == 0 || elapsed >= self.min_press_gap_scans;
|
||||||
pub fn buttons_pressed(&mut self) -> [bool; N] {
|
|
||||||
self.pressed
|
if can_register {
|
||||||
|
self.last_press_scan[index] = self.scan_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_register
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "std"))]
|
||||||
|
pub(crate) fn process_column_for_test(&mut self, column: usize) {
|
||||||
|
self.process_column(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
use embedded_hal::digital::ErrorType;
|
use std::rc::Rc;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
struct MockInputPin {
|
#[derive(Clone)]
|
||||||
state: Rc<Cell<bool>>,
|
struct MockPins {
|
||||||
|
row_state: Rc<Cell<bool>>,
|
||||||
|
column_state: Rc<Cell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockInputPin {
|
impl MockPins {
|
||||||
fn new(state: Rc<Cell<bool>>) -> Self {
|
fn new(row_state: Rc<Cell<bool>>, column_state: Rc<Cell<bool>>) -> Self {
|
||||||
Self { state }
|
// Build a button matrix scanner with default state tracking arrays.
|
||||||
|
Self {
|
||||||
|
row_state,
|
||||||
|
column_state,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorType for MockInputPin {
|
impl MatrixPinAccess<1, 1> for MockPins {
|
||||||
type Error = Infallible;
|
fn init_columns(&mut self) {
|
||||||
}
|
// Simulate the hardware by driving the single column high by default.
|
||||||
|
self.column_state.set(true);
|
||||||
impl InputPin for MockInputPin {
|
|
||||||
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
|
||||||
Ok(!self.state.get())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
fn set_column_low(&mut self, _column: usize) {
|
||||||
Ok(self.state.get())
|
// Drop the mock column low to emulate scanning behaviour.
|
||||||
|
self.column_state.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_column_high(&mut self, _column: usize) {
|
||||||
|
// Release the mock column back to the idle high state.
|
||||||
|
self.column_state.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_row(&mut self, _row: usize) -> bool {
|
||||||
|
// Return the mocked row state so tests can control pressed/unpressed.
|
||||||
|
self.row_state.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MockOutputPin {
|
fn fixture() -> (
|
||||||
state: Rc<Cell<bool>>,
|
ButtonMatrix<MockPins, 1, 1, 1>,
|
||||||
}
|
|
||||||
|
|
||||||
impl MockOutputPin {
|
|
||||||
fn new(state: Rc<Cell<bool>>) -> Self {
|
|
||||||
Self { state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorType for MockOutputPin {
|
|
||||||
type Error = Infallible;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputPin for MockOutputPin {
|
|
||||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
|
||||||
self.state.set(true);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
|
||||||
self.state.set(false);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matrix_fixture() -> (
|
|
||||||
ButtonMatrix<'static, 1, 1, 1>,
|
|
||||||
Rc<Cell<bool>>,
|
Rc<Cell<bool>>,
|
||||||
Rc<Cell<bool>>,
|
Rc<Cell<bool>>,
|
||||||
) {
|
) {
|
||||||
let row_state = Rc::new(Cell::new(false));
|
let row = Rc::new(Cell::new(false));
|
||||||
let col_state = Rc::new(Cell::new(false));
|
let column = Rc::new(Cell::new(true));
|
||||||
|
let pins = MockPins::new(row.clone(), column.clone());
|
||||||
let row_pin: &'static mut dyn InputPin<Error = Infallible> =
|
let matrix = ButtonMatrix::new(pins, 2, 3);
|
||||||
Box::leak(Box::new(MockInputPin::new(row_state.clone())));
|
(matrix, row, column)
|
||||||
let col_pin: &'static mut dyn OutputPin<Error = Infallible> =
|
|
||||||
Box::leak(Box::new(MockOutputPin::new(col_state.clone())));
|
|
||||||
|
|
||||||
let rows: &'static mut [&'static mut dyn InputPin<Error = Infallible>; 1] =
|
|
||||||
Box::leak(Box::new([row_pin]));
|
|
||||||
let cols: &'static mut [&'static mut dyn OutputPin<Error = Infallible>; 1] =
|
|
||||||
Box::leak(Box::new([col_pin]));
|
|
||||||
|
|
||||||
let matrix = ButtonMatrix::new(rows, cols, 2);
|
|
||||||
|
|
||||||
(matrix, row_state, col_state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_pins_sets_columns_high() {
|
fn debounce_requires_consecutive_scans() {
|
||||||
let (mut matrix, _row_state, col_state) = matrix_fixture();
|
// Debounce logic should require two consecutive pressed scans before registering.
|
||||||
assert!(!col_state.get());
|
let (mut matrix, row, _column) = fixture();
|
||||||
matrix.init_pins();
|
matrix.set_scan_counter(1);
|
||||||
assert!(col_state.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
row.set(true);
|
||||||
fn process_column_obeys_debounce() {
|
matrix.bump_scan_counter();
|
||||||
let (mut matrix, row_state, _col_state) = matrix_fixture();
|
matrix.process_column_for_test(0);
|
||||||
let mut states = matrix.buttons_pressed();
|
assert!(!matrix.buttons_pressed()[0]);
|
||||||
assert!(!states[0]);
|
|
||||||
row_state.set(true);
|
|
||||||
matrix.process_column(0);
|
|
||||||
matrix.process_column(0);
|
|
||||||
states = matrix.buttons_pressed();
|
|
||||||
assert!(states[0]);
|
|
||||||
|
|
||||||
row_state.set(false);
|
matrix.bump_scan_counter();
|
||||||
matrix.process_column(0);
|
matrix.process_column_for_test(0);
|
||||||
matrix.process_column(0);
|
assert!(matrix.buttons_pressed()[0]);
|
||||||
states = matrix.buttons_pressed();
|
|
||||||
assert!(!states[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! Button processing for CMDR Joystick 25
|
//! Button processing for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Responsibilities
|
//! Responsibilities
|
||||||
//! - Integrate button matrix results and extra pins
|
//! - Integrate button matrix results and extra pins
|
||||||
@ -8,7 +8,7 @@
|
|||||||
//! - Evaluate special combinations (bootloader, calibration, etc.)
|
//! - Evaluate special combinations (bootloader, calibration, etc.)
|
||||||
//! - Expose a compact state consumed by USB report generation
|
//! - Expose a compact state consumed by USB report generation
|
||||||
|
|
||||||
use crate::button_matrix::ButtonMatrix;
|
use crate::button_matrix::{ButtonMatrix, MatrixPins};
|
||||||
use crate::hardware::{AXIS_CENTER, BUTTON_COLS, BUTTON_ROWS, NUMBER_OF_BUTTONS};
|
use crate::hardware::{AXIS_CENTER, BUTTON_COLS, BUTTON_ROWS, NUMBER_OF_BUTTONS};
|
||||||
use crate::mapping::*;
|
use crate::mapping::*;
|
||||||
use embedded_hal::digital::InputPin;
|
use embedded_hal::digital::InputPin;
|
||||||
@ -17,6 +17,13 @@ use rp2040_hal::timer::Timer;
|
|||||||
// Total buttons including the two extra (non‑matrix) buttons
|
// Total buttons including the two extra (non‑matrix) buttons
|
||||||
pub const TOTAL_BUTTONS: usize = NUMBER_OF_BUTTONS + 2;
|
pub const TOTAL_BUTTONS: usize = NUMBER_OF_BUTTONS + 2;
|
||||||
|
|
||||||
|
pub type JoystickButtonMatrix = ButtonMatrix<
|
||||||
|
MatrixPins<{ BUTTON_ROWS }, { BUTTON_COLS }>,
|
||||||
|
{ BUTTON_ROWS },
|
||||||
|
{ BUTTON_COLS },
|
||||||
|
{ NUMBER_OF_BUTTONS },
|
||||||
|
>;
|
||||||
|
|
||||||
// ==================== BUTTON STRUCT ====================
|
// ==================== BUTTON STRUCT ====================
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
@ -60,6 +67,7 @@ pub struct ButtonManager {
|
|||||||
|
|
||||||
impl Default for ButtonManager {
|
impl Default for ButtonManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Build a button manager with default-initialized buttons and state flags.
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,10 +83,7 @@ impl ButtonManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update button states from the button matrix snapshot.
|
/// Update button states from the button matrix snapshot.
|
||||||
pub fn update_from_matrix(
|
pub fn update_from_matrix(&mut self, matrix: &mut JoystickButtonMatrix) {
|
||||||
&mut self,
|
|
||||||
matrix: &mut ButtonMatrix<BUTTON_ROWS, BUTTON_COLS, NUMBER_OF_BUTTONS>,
|
|
||||||
) {
|
|
||||||
for (index, key) in matrix.buttons_pressed().iter().enumerate() {
|
for (index, key) in matrix.buttons_pressed().iter().enumerate() {
|
||||||
self.buttons[index].pressed = *key;
|
self.buttons[index].pressed = *key;
|
||||||
}
|
}
|
||||||
@ -98,41 +103,42 @@ impl ButtonManager {
|
|||||||
|
|
||||||
/// Filter HAT switches so only a single direction (or center) can be active.
|
/// Filter HAT switches so only a single direction (or center) can be active.
|
||||||
pub fn filter_hat_switches(&mut self) {
|
pub fn filter_hat_switches(&mut self) {
|
||||||
// Filter left hat switch buttons
|
const LEFT_HAT_DIRECTIONS: [usize; 4] = [
|
||||||
for i in BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT {
|
BUTTON_TOP_LEFT_HAT_UP,
|
||||||
if (BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT)
|
BUTTON_TOP_LEFT_HAT_RIGHT,
|
||||||
.filter(|&j| j != i)
|
BUTTON_TOP_LEFT_HAT_DOWN,
|
||||||
.any(|j| self.buttons[j].pressed)
|
BUTTON_TOP_LEFT_HAT_LEFT,
|
||||||
{
|
];
|
||||||
self.buttons[i].pressed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix button state for center hat press on left hat
|
const RIGHT_HAT_DIRECTIONS: [usize; 4] = [
|
||||||
if self.buttons[BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT]
|
BUTTON_TOP_RIGHT_HAT_UP,
|
||||||
|
BUTTON_TOP_RIGHT_HAT_RIGHT,
|
||||||
|
BUTTON_TOP_RIGHT_HAT_DOWN,
|
||||||
|
BUTTON_TOP_RIGHT_HAT_LEFT,
|
||||||
|
];
|
||||||
|
|
||||||
|
self.reconcile_hat(&LEFT_HAT_DIRECTIONS, BUTTON_TOP_LEFT_HAT);
|
||||||
|
self.reconcile_hat(&RIGHT_HAT_DIRECTIONS, BUTTON_TOP_RIGHT_HAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconcile_hat(&mut self, directions: &[usize; 4], center: usize) {
|
||||||
|
// Normalize hat inputs by clearing the center and conflicting directions.
|
||||||
|
let pressed_count = directions
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.pressed)
|
.filter(|&&index| self.buttons[index].pressed)
|
||||||
{
|
.count();
|
||||||
self.buttons[BUTTON_TOP_LEFT_HAT].pressed = false;
|
|
||||||
|
if pressed_count == 0 {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter right hat switch buttons
|
self.buttons[center].pressed = false;
|
||||||
for i in BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT {
|
|
||||||
if (BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT)
|
if pressed_count >= 2 {
|
||||||
.filter(|&j| j != i)
|
for &index in directions.iter() {
|
||||||
.any(|j| self.buttons[j].pressed)
|
self.buttons[index].pressed = false;
|
||||||
{
|
|
||||||
self.buttons[i].pressed = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix button state for center hat press on right hat
|
|
||||||
if self.buttons[BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT]
|
|
||||||
.iter()
|
|
||||||
.any(|b| b.pressed)
|
|
||||||
{
|
|
||||||
self.buttons[BUTTON_TOP_RIGHT_HAT].pressed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update press types (short/long) and USB change flags; returns whether USB should be updated.
|
/// Update press types (short/long) and USB change flags; returns whether USB should be updated.
|
||||||
@ -232,6 +238,7 @@ impl ButtonManager {
|
|||||||
|
|
||||||
/// Get a change event for a button: Some(true)=press, Some(false)=release, None=no change.
|
/// Get a change event for a button: Some(true)=press, Some(false)=release, None=no change.
|
||||||
fn get_button_press_event(&self, button_index: usize) -> Option<bool> {
|
fn get_button_press_event(&self, button_index: usize) -> Option<bool> {
|
||||||
|
// Report the updated pressed state whenever it differs from the previous sample.
|
||||||
let button = &self.buttons[button_index];
|
let button = &self.buttons[button_index];
|
||||||
if button.pressed != button.previous_pressed {
|
if button.pressed != button.previous_pressed {
|
||||||
Some(button.pressed)
|
Some(button.pressed)
|
||||||
@ -261,6 +268,7 @@ impl ButtonManager {
|
|||||||
/// - Short press: on release (and if long not activated), activate `usb_button`
|
/// - Short press: on release (and if long not activated), activate `usb_button`
|
||||||
/// - USB press lifetime: auto‑release after a minimal hold so the host sees a pulse
|
/// - USB press lifetime: auto‑release after a minimal hold so the host sees a pulse
|
||||||
fn update_button_press_type(button: &mut Button, current_time: u32) {
|
fn update_button_press_type(button: &mut Button, current_time: u32) {
|
||||||
|
// Transition a single button between short/long press USB outputs.
|
||||||
const LONG_PRESS_THRESHOLD: u32 = 200;
|
const LONG_PRESS_THRESHOLD: u32 = 200;
|
||||||
|
|
||||||
// Pressing button
|
// Pressing button
|
||||||
@ -330,12 +338,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_manager_creation() {
|
fn test_button_manager_creation() {
|
||||||
|
// Button manager should allocate an entry for every physical button.
|
||||||
let manager = ButtonManager::new();
|
let manager = ButtonManager::new();
|
||||||
assert_eq!(manager.buttons.len(), TOTAL_BUTTONS);
|
assert_eq!(manager.buttons.len(), TOTAL_BUTTONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_default_state() {
|
fn test_button_default_state() {
|
||||||
|
// Default button instances start unpressed with no lingering USB state.
|
||||||
let button = Button::default();
|
let button = Button::default();
|
||||||
assert!(!button.pressed);
|
assert!(!button.pressed);
|
||||||
assert!(!button.previous_pressed);
|
assert!(!button.previous_pressed);
|
||||||
@ -345,6 +355,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_special_action_combinations() {
|
fn test_special_action_combinations() {
|
||||||
|
// The bootloader combo should trigger when all required buttons are pressed.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
// Test bootloader combination
|
// Test bootloader combination
|
||||||
@ -358,6 +369,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calibration_combination() {
|
fn test_calibration_combination() {
|
||||||
|
// Calibration combo should generate the start-calibration action.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
// Test calibration combination
|
// Test calibration combination
|
||||||
@ -371,6 +383,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throttle_hold_center() {
|
fn test_throttle_hold_center() {
|
||||||
|
// Throttle hold combo should capture the centered axis value when centered.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
manager.buttons[TH_BUTTON].pressed = true;
|
manager.buttons[TH_BUTTON].pressed = true;
|
||||||
manager.buttons[TH_BUTTON].previous_pressed = false;
|
manager.buttons[TH_BUTTON].previous_pressed = false;
|
||||||
@ -381,6 +394,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throttle_hold_value() {
|
fn test_throttle_hold_value() {
|
||||||
|
// Off-center throttle hold should capture the live axis value for hold.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
manager.buttons[TH_BUTTON].pressed = true;
|
manager.buttons[TH_BUTTON].pressed = true;
|
||||||
manager.buttons[TH_BUTTON].previous_pressed = false;
|
manager.buttons[TH_BUTTON].previous_pressed = false;
|
||||||
@ -392,6 +406,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_throttle_toggle() {
|
fn test_virtual_throttle_toggle() {
|
||||||
|
// Virtual throttle button should emit the toggle action when pressed.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
manager.buttons[VT_BUTTON].pressed = true;
|
manager.buttons[VT_BUTTON].pressed = true;
|
||||||
manager.buttons[VT_BUTTON].previous_pressed = false;
|
manager.buttons[VT_BUTTON].previous_pressed = false;
|
||||||
@ -402,6 +417,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hat_switch_filtering_left() {
|
fn test_hat_switch_filtering_left() {
|
||||||
|
// Left hat should clear center and conflicting directions when multiple inputs are active.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
// Press multiple directional buttons on left hat
|
// Press multiple directional buttons on left hat
|
||||||
@ -410,15 +426,17 @@ mod tests {
|
|||||||
|
|
||||||
manager.filter_hat_switches();
|
manager.filter_hat_switches();
|
||||||
|
|
||||||
// Only one should remain (implementation filters out conflicting ones)
|
// Multiple directions cancel the hat output completely
|
||||||
let pressed_count = (BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT)
|
let pressed_count = (BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT)
|
||||||
.filter(|&i| manager.buttons[i].pressed)
|
.filter(|&i| manager.buttons[i].pressed)
|
||||||
.count();
|
.count();
|
||||||
assert!(pressed_count <= 1);
|
assert_eq!(pressed_count, 0);
|
||||||
|
assert!(!manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hat_switch_filtering_right() {
|
fn test_hat_switch_filtering_right() {
|
||||||
|
// Right hat should behave the same way, disabling conflicts and the center button.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
// Press multiple directional buttons on right hat
|
// Press multiple directional buttons on right hat
|
||||||
@ -427,15 +445,17 @@ mod tests {
|
|||||||
|
|
||||||
manager.filter_hat_switches();
|
manager.filter_hat_switches();
|
||||||
|
|
||||||
// Only one should remain (implementation filters out conflicting ones)
|
// Multiple directions cancel the hat output completely
|
||||||
let pressed_count = (BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT)
|
let pressed_count = (BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT)
|
||||||
.filter(|&i| manager.buttons[i].pressed)
|
.filter(|&i| manager.buttons[i].pressed)
|
||||||
.count();
|
.count();
|
||||||
assert!(pressed_count <= 1);
|
assert_eq!(pressed_count, 0);
|
||||||
|
assert!(!manager.buttons[BUTTON_TOP_RIGHT_HAT].pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hat_center_button_filtering() {
|
fn test_hat_center_button_filtering() {
|
||||||
|
// Pressing a direction should suppress the corresponding hat center button.
|
||||||
let mut manager = ButtonManager::new();
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
// Press directional button and center button
|
// Press directional button and center button
|
||||||
@ -446,10 +466,50 @@ mod tests {
|
|||||||
|
|
||||||
// Center button should be disabled when directional is pressed
|
// Center button should be disabled when directional is pressed
|
||||||
assert!(!manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
assert!(!manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||||||
|
// But single direction should remain active
|
||||||
|
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hat_switch_single_direction_allowed() {
|
||||||
|
// A single direction press must remain active for both hats.
|
||||||
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
|
// Press only one directional button on left hat
|
||||||
|
manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed = true;
|
||||||
|
|
||||||
|
manager.filter_hat_switches();
|
||||||
|
|
||||||
|
// Single direction should remain active
|
||||||
|
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed);
|
||||||
|
|
||||||
|
// Test same for right hat
|
||||||
|
let mut manager = ButtonManager::new();
|
||||||
|
manager.buttons[BUTTON_TOP_RIGHT_HAT_DOWN].pressed = true;
|
||||||
|
|
||||||
|
manager.filter_hat_switches();
|
||||||
|
|
||||||
|
// Single direction should remain active
|
||||||
|
assert!(manager.buttons[BUTTON_TOP_RIGHT_HAT_DOWN].pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hat_center_button_works_alone() {
|
||||||
|
// When no direction is pressed, the center button should report as pressed.
|
||||||
|
let mut manager = ButtonManager::new();
|
||||||
|
|
||||||
|
// Press only center button (no directions)
|
||||||
|
manager.buttons[BUTTON_TOP_LEFT_HAT].pressed = true;
|
||||||
|
|
||||||
|
manager.filter_hat_switches();
|
||||||
|
|
||||||
|
// Center button should remain active when no directions are pressed
|
||||||
|
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_press_type_short_press() {
|
fn test_button_press_type_short_press() {
|
||||||
|
// Short presses should emit the primary USB button and flag a USB change.
|
||||||
let mut button = Button::default();
|
let mut button = Button::default();
|
||||||
button.usb_button = 1;
|
button.usb_button = 1;
|
||||||
button.enable_long_press = false;
|
button.enable_long_press = false;
|
||||||
@ -470,6 +530,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_press_type_long_press() {
|
fn test_button_press_type_long_press() {
|
||||||
|
// Long presses should switch to the alternate USB button and mark handled state.
|
||||||
let mut button = Button::default();
|
let mut button = Button::default();
|
||||||
button.usb_button = 1;
|
button.usb_button = 1;
|
||||||
button.usb_button_long = 2;
|
button.usb_button_long = 2;
|
||||||
@ -490,6 +551,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_press_type_long_press_auto_release_once() {
|
fn test_button_press_type_long_press_auto_release_once() {
|
||||||
|
// Non-hold long presses should auto-release once after triggering the long press.
|
||||||
let mut button = Button::default();
|
let mut button = Button::default();
|
||||||
button.usb_button_long = 2;
|
button.usb_button_long = 2;
|
||||||
button.enable_long_press = true;
|
button.enable_long_press = true;
|
||||||
@ -521,6 +583,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timer_integration_method_exists() {
|
fn test_timer_integration_method_exists() {
|
||||||
|
// Document that the timer-backed helper stays callable without hardware wiring.
|
||||||
let manager = ButtonManager::new();
|
let manager = ButtonManager::new();
|
||||||
|
|
||||||
// This test verifies the timer integration method signature and basic functionality
|
// This test verifies the timer integration method signature and basic functionality
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! Calibration management for CMDR Joystick 25
|
//! Calibration management for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Responsibilities
|
//! Responsibilities
|
||||||
//! - Manage the calibration lifecycle (start/active/stop)
|
//! - Manage the calibration lifecycle (start/active/stop)
|
||||||
@ -176,6 +176,7 @@ impl CalibrationManager {
|
|||||||
|
|
||||||
/// Reset each axis calibration to its current smoothed center.
|
/// Reset each axis calibration to its current smoothed center.
|
||||||
fn reset_axis_calibration(
|
fn reset_axis_calibration(
|
||||||
|
// Recenter all axis calibration values using current smoother readings.
|
||||||
&self,
|
&self,
|
||||||
axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS],
|
axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS],
|
||||||
smoothers: &[DynamicSmootherEcoI32; NBR_OF_GIMBAL_AXIS],
|
smoothers: &[DynamicSmootherEcoI32; NBR_OF_GIMBAL_AXIS],
|
||||||
@ -191,6 +192,7 @@ impl CalibrationManager {
|
|||||||
|
|
||||||
impl Default for CalibrationManager {
|
impl Default for CalibrationManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Construct a calibration manager using the Default implementation.
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,6 +207,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calibration_manager_creation() {
|
fn test_calibration_manager_creation() {
|
||||||
|
// Report whether calibration is currently active.
|
||||||
let manager = CalibrationManager::new();
|
let manager = CalibrationManager::new();
|
||||||
assert!(!manager.is_active());
|
assert!(!manager.is_active());
|
||||||
assert_eq!(manager.get_gimbal_mode(), GIMBAL_MODE_M10);
|
assert_eq!(manager.get_gimbal_mode(), GIMBAL_MODE_M10);
|
||||||
@ -212,6 +215,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calibration_state_management() {
|
fn test_calibration_state_management() {
|
||||||
|
// Start and stop transitions should flip the active flag accordingly.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
|
|
||||||
// Initially inactive
|
// Initially inactive
|
||||||
@ -228,6 +232,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gimbal_mode_management() {
|
fn test_gimbal_mode_management() {
|
||||||
|
// Manual mode setter should swap between M10 and M7 without side effects.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
|
|
||||||
// Default mode
|
// Default mode
|
||||||
@ -244,6 +249,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dynamic_calibration_inactive() {
|
fn test_dynamic_calibration_inactive() {
|
||||||
|
// Inactive calibration should ignore updates and leave min/max untouched.
|
||||||
let manager = CalibrationManager::new();
|
let manager = CalibrationManager::new();
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|
||||||
@ -271,6 +277,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dynamic_calibration_active() {
|
fn test_dynamic_calibration_active() {
|
||||||
|
// Active calibration should track new lows/highs as smoothed values change.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
manager.start_calibration();
|
manager.start_calibration();
|
||||||
|
|
||||||
@ -319,6 +326,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mode_selection_inactive() {
|
fn test_mode_selection_inactive() {
|
||||||
|
// Mode change commands should fail when calibration mode is not active.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|
||||||
@ -341,6 +349,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_axis_calibration_success() {
|
fn test_load_axis_calibration_success() {
|
||||||
|
// Successful EEPROM reads should populate the axis calibration tuple.
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|
||||||
// Mock EEPROM data simulating successful calibration read
|
// Mock EEPROM data simulating successful calibration read
|
||||||
@ -373,6 +382,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_axis_calibration_failure() {
|
fn test_load_axis_calibration_failure() {
|
||||||
|
// Failed EEPROM reads should leave default calibration values intact.
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
let original_axes = axes;
|
let original_axes = axes;
|
||||||
|
|
||||||
@ -391,6 +401,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_gimbal_mode_success() {
|
fn test_load_gimbal_mode_success() {
|
||||||
|
// Reading the stored gimbal mode should return the persisted value.
|
||||||
// Mock successful EEPROM read for M7 mode
|
// Mock successful EEPROM read for M7 mode
|
||||||
let mut read_fn = |addr: u32| {
|
let mut read_fn = |addr: u32| {
|
||||||
match addr {
|
match addr {
|
||||||
@ -405,6 +416,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_gimbal_mode_failure() {
|
fn test_load_gimbal_mode_failure() {
|
||||||
|
// Read failures should fall back to the default M10 mode.
|
||||||
// Mock EEPROM read failure
|
// Mock EEPROM read failure
|
||||||
let mut read_fn = |_addr: u32| Err(());
|
let mut read_fn = |_addr: u32| Err(());
|
||||||
|
|
||||||
@ -414,6 +426,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_calibration_inactive() {
|
fn test_update_calibration_inactive() {
|
||||||
|
// When calibration is inactive, mode/set/save helpers should be no-ops.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|
||||||
@ -442,6 +455,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_mode_selection_m10_command() {
|
fn test_process_mode_selection_m10_command() {
|
||||||
|
// Active M10 command should set mode and recenter axes using smoother values.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
manager.start_calibration();
|
manager.start_calibration();
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
@ -469,6 +483,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_mode_selection_m7_command() {
|
fn test_process_mode_selection_m7_command() {
|
||||||
|
// Active M7 command should likewise set mode and recenter axes.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
manager.start_calibration();
|
manager.start_calibration();
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
@ -496,6 +511,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_save_calibration_command() {
|
fn test_save_calibration_command() {
|
||||||
|
// Successful saves should write EEPROM data and exit calibration mode.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
manager.start_calibration();
|
manager.start_calibration();
|
||||||
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
@ -515,6 +531,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_save_calibration_failure_keeps_active() {
|
fn test_save_calibration_failure_keeps_active() {
|
||||||
|
// Write failures should keep calibration active for retrials.
|
||||||
let mut manager = CalibrationManager::new();
|
let mut manager = CalibrationManager::new();
|
||||||
manager.start_calibration();
|
manager.start_calibration();
|
||||||
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
@ -529,6 +546,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_save_calibration_inactive() {
|
fn test_save_calibration_inactive() {
|
||||||
|
// Save attempts while inactive should no-op without touching storage.
|
||||||
let mut manager = CalibrationManager::new(); // Note: not starting calibration
|
let mut manager = CalibrationManager::new(); // Note: not starting calibration
|
||||||
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_expo_lut_boundaries() {
|
fn test_generate_expo_lut_boundaries() {
|
||||||
|
// Ensure expo LUT boundary entries clamp to ADC range.
|
||||||
let lut = generate_expo_lut(0.5);
|
let lut = generate_expo_lut(0.5);
|
||||||
assert_eq!(lut[0], ADC_MIN);
|
assert_eq!(lut[0], ADC_MIN);
|
||||||
assert_eq!(lut[ADC_MAX as usize], ADC_MAX);
|
assert_eq!(lut[ADC_MAX as usize], ADC_MAX);
|
||||||
@ -86,6 +87,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_expo_lut_center_point() {
|
fn test_generate_expo_lut_center_point() {
|
||||||
|
// Midpoint of the expo LUT stays near the physical center.
|
||||||
let lut = generate_expo_lut(0.5);
|
let lut = generate_expo_lut(0.5);
|
||||||
let center_index = (ADC_MAX / 2) as usize;
|
let center_index = (ADC_MAX / 2) as usize;
|
||||||
let center_value = lut[center_index];
|
let center_value = lut[center_index];
|
||||||
@ -94,6 +96,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_expo_lut_different_factors() {
|
fn test_generate_expo_lut_different_factors() {
|
||||||
|
// Different expo factors should yield distinct transfer functions at the same index.
|
||||||
let lut_linear = generate_expo_lut(0.0);
|
let lut_linear = generate_expo_lut(0.0);
|
||||||
let lut_expo = generate_expo_lut(1.0);
|
let lut_expo = generate_expo_lut(1.0);
|
||||||
let quarter_point = (ADC_MAX / 4) as usize;
|
let quarter_point = (ADC_MAX / 4) as usize;
|
||||||
@ -102,6 +105,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_expo_curve_no_expo() {
|
fn test_apply_expo_curve_no_expo() {
|
||||||
|
// With zero expo factor the lookup table should behave linearly.
|
||||||
let lut = generate_expo_lut(0.0);
|
let lut = generate_expo_lut(0.0);
|
||||||
let input_value = 1000u16;
|
let input_value = 1000u16;
|
||||||
let result = apply_expo_curve(input_value, &lut);
|
let result = apply_expo_curve(input_value, &lut);
|
||||||
@ -111,6 +115,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_expo_curve_with_expo() {
|
fn test_apply_expo_curve_with_expo() {
|
||||||
|
// Non-zero expo factor should change outputs relative to the linear LUT.
|
||||||
let lut_linear = generate_expo_lut(0.0);
|
let lut_linear = generate_expo_lut(0.0);
|
||||||
let lut_expo = generate_expo_lut(0.5);
|
let lut_expo = generate_expo_lut(0.5);
|
||||||
let test_value = 1000u16;
|
let test_value = 1000u16;
|
||||||
@ -123,6 +128,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_expo_curve_center_unchanged() {
|
fn test_apply_expo_curve_center_unchanged() {
|
||||||
|
// Expo mapping should leave the center point near the mechanical center.
|
||||||
let lut = generate_expo_lut(0.5);
|
let lut = generate_expo_lut(0.5);
|
||||||
let result = apply_expo_curve(AXIS_CENTER, &lut);
|
let result = apply_expo_curve(AXIS_CENTER, &lut);
|
||||||
// Center point should remain close to center
|
// Center point should remain close to center
|
||||||
@ -131,21 +137,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_constrain_within_range() {
|
fn test_constrain_within_range() {
|
||||||
|
// Values inside limits should be returned unchanged by constrain.
|
||||||
assert_eq!(constrain(50u16, 0u16, 100u16), 50u16);
|
assert_eq!(constrain(50u16, 0u16, 100u16), 50u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_constrain_above_range() {
|
fn test_constrain_above_range() {
|
||||||
|
// Values above the upper bound should clamp to the max.
|
||||||
assert_eq!(constrain(150u16, 0u16, 100u16), 100u16);
|
assert_eq!(constrain(150u16, 0u16, 100u16), 100u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_constrain_below_range() {
|
fn test_constrain_below_range() {
|
||||||
|
// Values below the lower bound should clamp to the min.
|
||||||
assert_eq!(constrain(0u16, 50u16, 100u16), 50u16);
|
assert_eq!(constrain(0u16, 50u16, 100u16), 50u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expo_integration_real_world_values() {
|
fn test_expo_integration_real_world_values() {
|
||||||
|
// Representative axis values should always map within the ADC domain.
|
||||||
let lut = generate_expo_lut(0.3);
|
let lut = generate_expo_lut(0.3);
|
||||||
let test_values = [500u16, 1000u16, 2000u16, 3000u16];
|
let test_values = [500u16, 1000u16, 2000u16, 3000u16];
|
||||||
|
|
||||||
|
|||||||
@ -1,190 +1,195 @@
|
|||||||
//! Hardware configuration for CMDR Joystick 25 (RP2040)
|
//! Hardware configuration for CMDR Joystick (RP2040)
|
||||||
//!
|
//!
|
||||||
//! Centralizes board constants, GPIO mappings, timing cadences and helper
|
//! Mirrors the structure introduced for the CMDR Keyboard firmware so that
|
||||||
//! macros to keep hardware details out of business logic.
|
//! bring-up, pin management, and timing constants follow the same layout.
|
||||||
|
|
||||||
|
use rp2040_hal::gpio::Pins;
|
||||||
|
use rp2040_hal::gpio::{
|
||||||
|
self, DynPinId, FunctionI2C, FunctionPio0, FunctionSioInput, FunctionSioOutput, Pin, PullNone,
|
||||||
|
PullUp,
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== CRYSTAL AND USB CONSTANTS ====================
|
|
||||||
/// External crystal frequency (Hz).
|
/// External crystal frequency (Hz).
|
||||||
pub const XTAL_FREQ_HZ: u32 = 12_000_000u32;
|
pub const XTAL_FREQ_HZ: u32 = 12_000_000;
|
||||||
/// USB Vendor ID.
|
|
||||||
|
/// USB Vendor ID/Product ID.
|
||||||
pub const USB_VID: u16 = 0x1209;
|
pub const USB_VID: u16 = 0x1209;
|
||||||
/// USB Product ID.
|
|
||||||
pub const USB_PID: u16 = 0x0002;
|
pub const USB_PID: u16 = 0x0002;
|
||||||
|
|
||||||
// ==================== JOYSTICK CONSTANTS ====================
|
/// Button matrix geometry (rows/cols) and count.
|
||||||
/// Button matrix geometry (rows).
|
|
||||||
pub const BUTTON_ROWS: usize = 5;
|
pub const BUTTON_ROWS: usize = 5;
|
||||||
/// Button matrix geometry (columns).
|
|
||||||
pub const BUTTON_COLS: usize = 5;
|
pub const BUTTON_COLS: usize = 5;
|
||||||
/// Total number of matrix buttons.
|
|
||||||
pub const NUMBER_OF_BUTTONS: usize = BUTTON_ROWS * BUTTON_COLS;
|
pub const NUMBER_OF_BUTTONS: usize = BUTTON_ROWS * BUTTON_COLS;
|
||||||
/// ADC raw minimum (12‑bit).
|
|
||||||
|
/// ADC characteristics.
|
||||||
pub const ADC_MIN: u16 = 0;
|
pub const ADC_MIN: u16 = 0;
|
||||||
/// ADC raw maximum (12‑bit).
|
|
||||||
pub const ADC_MAX: u16 = 4095;
|
pub const ADC_MAX: u16 = 4095;
|
||||||
/// Logical axis center.
|
|
||||||
pub const AXIS_CENTER: u16 = (ADC_MIN + ADC_MAX) / 2;
|
pub const AXIS_CENTER: u16 = (ADC_MIN + ADC_MAX) / 2;
|
||||||
/// Number of physical gimbal axes.
|
|
||||||
pub const NBR_OF_GIMBAL_AXIS: usize = 4;
|
pub const NBR_OF_GIMBAL_AXIS: usize = 4;
|
||||||
/// Debounce threshold (in scans) for the matrix.
|
|
||||||
pub const DEBOUNCE: u8 = 10;
|
/// Debounce thresholds.
|
||||||
/// Bytes reserved in EEPROM for calibration data + gimbal mode.
|
pub const MATRIX_DEBOUNCE_SCANS: u8 = 15;
|
||||||
|
pub const MIN_PRESS_SPACING_SCANS: u32 = 25; // ~5ms @ 200µs cadence
|
||||||
|
|
||||||
|
/// EEPROM storage length (calibration data + gimbal mode).
|
||||||
pub const EEPROM_DATA_LENGTH: usize = 25;
|
pub const EEPROM_DATA_LENGTH: usize = 25;
|
||||||
|
|
||||||
// ==================== GPIO PIN DEFINITIONS ====================
|
|
||||||
/// Logical mapping between board functions and GPIO numbers.
|
|
||||||
pub mod pins {
|
|
||||||
/// Extra buttons (TX/RX pins)
|
|
||||||
pub const LEFT_EXTRA_BUTTON_PIN: u8 = 1;
|
|
||||||
pub const RIGHT_EXTRA_BUTTON_PIN: u8 = 0;
|
|
||||||
|
|
||||||
/// Button matrix row pins
|
|
||||||
pub const BUTTON_ROW_PIN_0: u8 = 6;
|
|
||||||
pub const BUTTON_ROW_PIN_1: u8 = 8;
|
|
||||||
pub const BUTTON_ROW_PIN_2: u8 = 4;
|
|
||||||
pub const BUTTON_ROW_PIN_3: u8 = 7;
|
|
||||||
pub const BUTTON_ROW_PIN_4: u8 = 5;
|
|
||||||
|
|
||||||
/// Button matrix column pins
|
|
||||||
pub const BUTTON_COL_PIN_0: u8 = 9;
|
|
||||||
pub const BUTTON_COL_PIN_1: u8 = 10;
|
|
||||||
pub const BUTTON_COL_PIN_2: u8 = 11;
|
|
||||||
pub const BUTTON_COL_PIN_3: u8 = 12;
|
|
||||||
pub const BUTTON_COL_PIN_4: u8 = 13;
|
|
||||||
|
|
||||||
/// ADC pins for gimbal axes
|
|
||||||
pub const ADC_LEFT_X_PIN: u8 = 29;
|
|
||||||
pub const ADC_LEFT_Y_PIN: u8 = 28;
|
|
||||||
pub const ADC_RIGHT_X_PIN: u8 = 27;
|
|
||||||
pub const ADC_RIGHT_Y_PIN: u8 = 26;
|
|
||||||
|
|
||||||
/// Status LED pin
|
|
||||||
pub const STATUS_LED_PIN: u8 = 16;
|
|
||||||
|
|
||||||
/// I2C pins for EEPROM
|
|
||||||
pub const I2C_SDA_PIN: u8 = 14;
|
|
||||||
pub const I2C_SCL_PIN: u8 = 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== I2C CONFIGURATION ====================
|
|
||||||
/// I2C frequency and system clock helpers for the EEPROM bus.
|
|
||||||
pub mod i2c {
|
|
||||||
use fugit::{Rate, RateExtU32};
|
|
||||||
|
|
||||||
pub const I2C_FREQUENCY_HZ: u32 = 400_000;
|
|
||||||
pub fn i2c_frequency() -> Rate<u32, 1, 1> {
|
|
||||||
I2C_FREQUENCY_HZ.Hz()
|
|
||||||
}
|
|
||||||
pub const SYSTEM_CLOCK_HZ: u32 = 125_000_000;
|
|
||||||
pub fn system_clock() -> Rate<u32, 1, 1> {
|
|
||||||
SYSTEM_CLOCK_HZ.Hz()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== TIMER INTERVALS ====================
|
|
||||||
/// Cadences for periodic firmware tasks.
|
|
||||||
pub mod timers {
|
|
||||||
/// Status LED update interval (ms).
|
|
||||||
pub const STATUS_LED_INTERVAL_MS: u32 = 40;
|
|
||||||
|
|
||||||
/// Button matrix scan interval (µs).
|
|
||||||
pub const SCAN_INTERVAL_US: u32 = 200;
|
|
||||||
|
|
||||||
/// USB HID report interval (ms).
|
|
||||||
pub const USB_UPDATE_INTERVAL_MS: u32 = 1;
|
|
||||||
|
|
||||||
/// USB activity timeout (ms) - stop sending reports after this period of inactivity.
|
|
||||||
pub const USB_ACTIVITY_TIMEOUT_MS: u32 = 5_000; // 5 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== USB DEVICE CONFIGURATION ====================
|
|
||||||
/// USB string descriptors.
|
/// USB string descriptors.
|
||||||
pub mod usb {
|
pub mod usb {
|
||||||
pub const MANUFACTURER: &str = "CMtec";
|
pub const MANUFACTURER: &str = "CMtec";
|
||||||
pub const PRODUCT: &str = "CMDR Joystick 25";
|
pub const PRODUCT: &str = "CMDR Joystick";
|
||||||
pub const SERIAL_NUMBER: &str = "0001";
|
pub const SERIAL_NUMBER: &str = "0001";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== PIN ACCESS MACROS ====================
|
/// Timing cadences.
|
||||||
|
pub mod timers {
|
||||||
/// Macro to access typed GPIO pins using board constants.
|
pub const STATUS_LED_INTERVAL_MS: u32 = 40;
|
||||||
/// Avoids scattering raw GPIO numbers; each arm references the constant it maps.
|
pub const SCAN_INTERVAL_US: u32 = 200;
|
||||||
#[macro_export]
|
pub const USB_UPDATE_INTERVAL_MS: u32 = 1;
|
||||||
macro_rules! get_pin {
|
pub const USB_ACTIVITY_TIMEOUT_MS: u32 = 5_000;
|
||||||
($pins:expr, left_extra_button) => {{
|
}
|
||||||
const _: u8 = $crate::hardware::pins::LEFT_EXTRA_BUTTON_PIN;
|
|
||||||
$pins.gpio1
|
/// I2C helpers.
|
||||||
}};
|
pub mod i2c {
|
||||||
($pins:expr, right_extra_button) => {{
|
use eeprom24x::SlaveAddr;
|
||||||
const _: u8 = $crate::hardware::pins::RIGHT_EXTRA_BUTTON_PIN;
|
use fugit::{Rate, RateExtU32};
|
||||||
$pins.gpio0
|
|
||||||
}};
|
pub const FREQUENCY_HZ: u32 = 400_000;
|
||||||
($pins:expr, button_row_0) => {{
|
pub const SYSTEM_CLOCK_HZ: u32 = 125_000_000;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_ROW_PIN_0;
|
|
||||||
$pins.gpio6
|
pub fn frequency() -> Rate<u32, 1, 1> {
|
||||||
}};
|
FREQUENCY_HZ.Hz()
|
||||||
($pins:expr, button_row_1) => {{
|
}
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_ROW_PIN_1;
|
|
||||||
$pins.gpio8
|
pub fn system_clock() -> Rate<u32, 1, 1> {
|
||||||
}};
|
SYSTEM_CLOCK_HZ.Hz()
|
||||||
($pins:expr, button_row_2) => {{
|
}
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_ROW_PIN_2;
|
|
||||||
$pins.gpio4
|
pub const EEPROM_ADDRESS: SlaveAddr = SlaveAddr::Alternative(false, false, false);
|
||||||
}};
|
}
|
||||||
($pins:expr, button_row_3) => {{
|
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_ROW_PIN_3;
|
/// Raw GPIO constants retained for documentation/reference.
|
||||||
$pins.gpio7
|
pub mod pins {
|
||||||
}};
|
pub const LEFT_EXTRA_BUTTON: u8 = 1;
|
||||||
($pins:expr, button_row_4) => {{
|
pub const RIGHT_EXTRA_BUTTON: u8 = 0;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_ROW_PIN_4;
|
pub const BUTTON_ROW_0: u8 = 6;
|
||||||
$pins.gpio5
|
pub const BUTTON_ROW_1: u8 = 8;
|
||||||
}};
|
pub const BUTTON_ROW_2: u8 = 4;
|
||||||
($pins:expr, button_col_0) => {{
|
pub const BUTTON_ROW_3: u8 = 7;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_COL_PIN_0;
|
pub const BUTTON_ROW_4: u8 = 5;
|
||||||
$pins.gpio9
|
pub const BUTTON_COL_0: u8 = 9;
|
||||||
}};
|
pub const BUTTON_COL_1: u8 = 10;
|
||||||
($pins:expr, button_col_1) => {{
|
pub const BUTTON_COL_2: u8 = 11;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_COL_PIN_1;
|
pub const BUTTON_COL_3: u8 = 12;
|
||||||
$pins.gpio10
|
pub const BUTTON_COL_4: u8 = 13;
|
||||||
}};
|
pub const ADC_LEFT_X: u8 = 29;
|
||||||
($pins:expr, button_col_2) => {{
|
pub const ADC_LEFT_Y: u8 = 28;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_COL_PIN_2;
|
pub const ADC_RIGHT_X: u8 = 27;
|
||||||
$pins.gpio11
|
pub const ADC_RIGHT_Y: u8 = 26;
|
||||||
}};
|
pub const STATUS_LED: u8 = 16;
|
||||||
($pins:expr, button_col_3) => {{
|
pub const I2C_SDA: u8 = 14;
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_COL_PIN_3;
|
pub const I2C_SCL: u8 = 15;
|
||||||
$pins.gpio12
|
}
|
||||||
}};
|
|
||||||
($pins:expr, button_col_4) => {{
|
/// Matrix row pins (dynamic to simplify scanning code).
|
||||||
const _: u8 = $crate::hardware::pins::BUTTON_COL_PIN_4;
|
pub type MatrixRowPin = Pin<DynPinId, FunctionSioInput, PullUp>;
|
||||||
$pins.gpio13
|
/// Matrix column pins (dynamic push-pull outputs).
|
||||||
}};
|
pub type MatrixColPin = Pin<DynPinId, FunctionSioOutput, PullNone>;
|
||||||
($pins:expr, adc_left_x) => {{
|
/// Extra buttons (pull-up inputs).
|
||||||
const _: u8 = $crate::hardware::pins::ADC_LEFT_X_PIN;
|
pub type ExtraButtonPin = Pin<DynPinId, FunctionSioInput, PullUp>;
|
||||||
$pins.gpio29
|
/// Status LED pin configured for PIO output.
|
||||||
}};
|
pub type StatusLedPin = Pin<gpio::bank0::Gpio16, FunctionPio0, PullNone>;
|
||||||
($pins:expr, adc_left_y) => {{
|
/// I2C SDA/SCL pins after reconfiguration.
|
||||||
const _: u8 = $crate::hardware::pins::ADC_LEFT_Y_PIN;
|
pub type I2cSdaPin = Pin<gpio::bank0::Gpio14, FunctionI2C, PullUp>;
|
||||||
$pins.gpio28
|
pub type I2cSclPin = Pin<gpio::bank0::Gpio15, FunctionI2C, PullUp>;
|
||||||
}};
|
|
||||||
($pins:expr, adc_right_x) => {{
|
/// Analog axis input pins (remain as SIO inputs until wrapped by `AdcPin`).
|
||||||
const _: u8 = $crate::hardware::pins::ADC_RIGHT_X_PIN;
|
pub struct AxisInputs {
|
||||||
$pins.gpio27
|
pub left_x: Pin<gpio::bank0::Gpio29, FunctionSioInput, PullNone>,
|
||||||
}};
|
pub left_y: Pin<gpio::bank0::Gpio28, FunctionSioInput, PullNone>,
|
||||||
($pins:expr, adc_right_y) => {{
|
pub right_x: Pin<gpio::bank0::Gpio27, FunctionSioInput, PullNone>,
|
||||||
const _: u8 = $crate::hardware::pins::ADC_RIGHT_Y_PIN;
|
pub right_y: Pin<gpio::bank0::Gpio26, FunctionSioInput, PullNone>,
|
||||||
$pins.gpio26
|
}
|
||||||
}};
|
|
||||||
($pins:expr, status_led) => {{
|
/// Bundle returned by `split_board_pins`.
|
||||||
const _: u8 = $crate::hardware::pins::STATUS_LED_PIN;
|
pub struct BoardPins {
|
||||||
$pins.gpio16
|
pub matrix_rows: [MatrixRowPin; BUTTON_ROWS],
|
||||||
}};
|
pub matrix_cols: [MatrixColPin; BUTTON_COLS],
|
||||||
($pins:expr, i2c_sda) => {{
|
pub left_extra_button: ExtraButtonPin,
|
||||||
const _: u8 = $crate::hardware::pins::I2C_SDA_PIN;
|
pub right_extra_button: ExtraButtonPin,
|
||||||
$pins.gpio14
|
pub axis_inputs: AxisInputs,
|
||||||
}};
|
pub status_led: StatusLedPin,
|
||||||
($pins:expr, i2c_scl) => {{
|
pub i2c_sda: I2cSdaPin,
|
||||||
const _: u8 = $crate::hardware::pins::I2C_SCL_PIN;
|
pub i2c_scl: I2cSclPin,
|
||||||
$pins.gpio15
|
}
|
||||||
}};
|
|
||||||
|
impl BoardPins {
|
||||||
|
pub fn new(pins: Pins) -> Self {
|
||||||
|
let row0 = pins.gpio6.into_pull_up_input().into_dyn_pin();
|
||||||
|
let row1 = pins.gpio8.into_pull_up_input().into_dyn_pin();
|
||||||
|
let row2 = pins.gpio4.into_pull_up_input().into_dyn_pin();
|
||||||
|
let row3 = pins.gpio7.into_pull_up_input().into_dyn_pin();
|
||||||
|
let row4 = pins.gpio5.into_pull_up_input().into_dyn_pin();
|
||||||
|
|
||||||
|
let col0 = pins
|
||||||
|
.gpio9
|
||||||
|
.into_push_pull_output()
|
||||||
|
.into_pull_type::<PullNone>()
|
||||||
|
.into_dyn_pin();
|
||||||
|
let col1 = pins
|
||||||
|
.gpio10
|
||||||
|
.into_push_pull_output()
|
||||||
|
.into_pull_type::<PullNone>()
|
||||||
|
.into_dyn_pin();
|
||||||
|
let col2 = pins
|
||||||
|
.gpio11
|
||||||
|
.into_push_pull_output()
|
||||||
|
.into_pull_type::<PullNone>()
|
||||||
|
.into_dyn_pin();
|
||||||
|
let col3 = pins
|
||||||
|
.gpio12
|
||||||
|
.into_push_pull_output()
|
||||||
|
.into_pull_type::<PullNone>()
|
||||||
|
.into_dyn_pin();
|
||||||
|
let col4 = pins
|
||||||
|
.gpio13
|
||||||
|
.into_push_pull_output()
|
||||||
|
.into_pull_type::<PullNone>()
|
||||||
|
.into_dyn_pin();
|
||||||
|
|
||||||
|
let left_extra = pins.gpio1.into_pull_up_input().into_dyn_pin();
|
||||||
|
let right_extra = pins.gpio0.into_pull_up_input().into_dyn_pin();
|
||||||
|
|
||||||
|
let axis_inputs = AxisInputs {
|
||||||
|
left_x: pins.gpio29.into_floating_input(),
|
||||||
|
left_y: pins.gpio28.into_floating_input(),
|
||||||
|
right_x: pins.gpio27.into_floating_input(),
|
||||||
|
right_y: pins.gpio26.into_floating_input(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let status_led = pins
|
||||||
|
.gpio16
|
||||||
|
.into_function::<FunctionPio0>()
|
||||||
|
.into_pull_type::<PullNone>();
|
||||||
|
|
||||||
|
let i2c_sda = pins
|
||||||
|
.gpio14
|
||||||
|
.into_function::<FunctionI2C>()
|
||||||
|
.into_pull_type::<PullUp>();
|
||||||
|
let i2c_scl = pins
|
||||||
|
.gpio15
|
||||||
|
.into_function::<FunctionI2C>()
|
||||||
|
.into_pull_type::<PullUp>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
matrix_rows: [row0, row1, row2, row3, row4],
|
||||||
|
matrix_cols: [col0, col1, col2, col3, col4],
|
||||||
|
left_extra_button: left_extra,
|
||||||
|
right_extra_button: right_extra,
|
||||||
|
axis_inputs,
|
||||||
|
status_led,
|
||||||
|
i2c_sda,
|
||||||
|
i2c_scl,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
321
rp2040/src/joystick.rs
Normal file
321
rp2040/src/joystick.rs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
//! Runtime state for the CMDR Joystick.
|
||||||
|
//!
|
||||||
|
//! This mirrors the `KeyboardState` abstraction from the keyboard refactor and
|
||||||
|
//! concentrates axis/button/calibration logic alongside USB bookkeeping.
|
||||||
|
|
||||||
|
use crate::axis::AxisManager;
|
||||||
|
use crate::buttons::{ButtonManager, SpecialAction};
|
||||||
|
use crate::calibration::CalibrationManager;
|
||||||
|
use crate::expo::ExpoLUT;
|
||||||
|
use crate::hardware;
|
||||||
|
use crate::status::SystemState;
|
||||||
|
use crate::usb_joystick_device::JoystickReport;
|
||||||
|
use crate::usb_report::get_joystick_report;
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use dyn_smooth::DynamicSmootherEcoI32;
|
||||||
|
use embedded_hal::digital::InputPin;
|
||||||
|
use rp2040_hal::timer::Timer;
|
||||||
|
use usb_device::device::UsbDeviceState;
|
||||||
|
|
||||||
|
pub struct UsbState {
|
||||||
|
pub initialized: bool,
|
||||||
|
pub active: bool,
|
||||||
|
pub suspended: bool,
|
||||||
|
pub send_pending: bool,
|
||||||
|
pub wake_on_input: bool,
|
||||||
|
pub idle_mode: bool,
|
||||||
|
pub activity: bool,
|
||||||
|
activity_elapsed_ms: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsbState {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
initialized: false,
|
||||||
|
active: false,
|
||||||
|
suspended: false,
|
||||||
|
send_pending: false,
|
||||||
|
wake_on_input: false,
|
||||||
|
idle_mode: false,
|
||||||
|
activity: false,
|
||||||
|
activity_elapsed_ms: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_poll(&mut self) {
|
||||||
|
// Called on every USB poll; mark device active and wake from idle.
|
||||||
|
if !self.initialized {
|
||||||
|
self.initialized = true;
|
||||||
|
}
|
||||||
|
if !self.active {
|
||||||
|
self.mark_activity();
|
||||||
|
}
|
||||||
|
self.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_activity(&mut self) {
|
||||||
|
// Flag that input activity occurred and reset idle counters.
|
||||||
|
self.activity = true;
|
||||||
|
self.activity_elapsed_ms = 0;
|
||||||
|
self.idle_mode = false;
|
||||||
|
self.send_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input_activity(&mut self) {
|
||||||
|
// Treat input changes as activity and request wake from suspend.
|
||||||
|
self.mark_activity();
|
||||||
|
if self.suspended && self.wake_on_input {
|
||||||
|
self.wake_on_input = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_suspend_change(&mut self, state: UsbDeviceState) {
|
||||||
|
// Update suspend bookkeeping when USB state changes.
|
||||||
|
let was_suspended = self.suspended;
|
||||||
|
self.suspended = state == UsbDeviceState::Suspend;
|
||||||
|
|
||||||
|
match (was_suspended, self.suspended) {
|
||||||
|
(true, false) => {
|
||||||
|
self.mark_activity();
|
||||||
|
self.wake_on_input = false;
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
self.idle_mode = true;
|
||||||
|
self.activity = false;
|
||||||
|
self.send_pending = false;
|
||||||
|
self.wake_on_input = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance_idle_timer(&mut self, interval_ms: u32) {
|
||||||
|
// Age the activity timer, transitioning to idle after the timeout.
|
||||||
|
if !self.activity {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.activity_elapsed_ms = self.activity_elapsed_ms.saturating_add(interval_ms);
|
||||||
|
if self.activity_elapsed_ms >= hardware::timers::USB_ACTIVITY_TIMEOUT_MS {
|
||||||
|
self.activity = false;
|
||||||
|
self.activity_elapsed_ms = 0;
|
||||||
|
self.idle_mode = true;
|
||||||
|
self.send_pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acknowledge_report(&mut self) {
|
||||||
|
// Reset pending state once the host accepts a report.
|
||||||
|
self.send_pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JoystickState {
|
||||||
|
axis_manager: AxisManager,
|
||||||
|
button_manager: ButtonManager,
|
||||||
|
calibration_manager: CalibrationManager,
|
||||||
|
smoother: [DynamicSmootherEcoI32; hardware::NBR_OF_GIMBAL_AXIS],
|
||||||
|
expo_primary: ExpoLUT,
|
||||||
|
expo_virtual: ExpoLUT,
|
||||||
|
vt_enable: bool,
|
||||||
|
usb: UsbState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JoystickState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// Initialize managers, smoothers, expo curves, and USB state.
|
||||||
|
Self {
|
||||||
|
axis_manager: AxisManager::new(),
|
||||||
|
button_manager: ButtonManager::new(),
|
||||||
|
calibration_manager: CalibrationManager::new(),
|
||||||
|
smoother: AxisManager::create_smoothers(),
|
||||||
|
expo_primary: ExpoLUT::new(0.3),
|
||||||
|
expo_virtual: ExpoLUT::new(0.6),
|
||||||
|
vt_enable: false,
|
||||||
|
usb: UsbState::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_calibration<R>(&mut self, mut read_fn: R)
|
||||||
|
where
|
||||||
|
R: FnMut(u32) -> Result<u8, ()>,
|
||||||
|
{
|
||||||
|
// Fetch stored axis calibration and gimbal mode from persistent storage.
|
||||||
|
CalibrationManager::load_axis_calibration(&mut self.axis_manager.axes, &mut read_fn);
|
||||||
|
let gimbal_mode = CalibrationManager::load_gimbal_mode(&mut read_fn);
|
||||||
|
self.axis_manager.set_gimbal_mode(gimbal_mode);
|
||||||
|
self.calibration_manager.set_gimbal_mode(gimbal_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_button_states<L, R>(
|
||||||
|
&mut self,
|
||||||
|
matrix: &mut crate::board::JoystickMatrix,
|
||||||
|
left_button: &mut L,
|
||||||
|
right_button: &mut R,
|
||||||
|
) where
|
||||||
|
L: InputPin,
|
||||||
|
R: InputPin,
|
||||||
|
L::Error: Debug,
|
||||||
|
R::Error: Debug,
|
||||||
|
{
|
||||||
|
// Refresh matrix-driven and discrete button inputs, then normalize hats.
|
||||||
|
self.button_manager.update_from_matrix(matrix);
|
||||||
|
self.button_manager
|
||||||
|
.update_extra_buttons(left_button, right_button);
|
||||||
|
self.button_manager.filter_hat_switches();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize_button_logic(&mut self, timer: &Timer) -> bool {
|
||||||
|
// Update per-button timers and USB state using the shared hardware timer.
|
||||||
|
self.button_manager.process_button_logic_with_timer(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_special_action(&self) -> SpecialAction {
|
||||||
|
// Inspect button state for bootloader/calibration/hold command combinations.
|
||||||
|
self.button_manager.check_special_combinations(
|
||||||
|
self.axis_manager.get_value_before_hold(),
|
||||||
|
self.calibration_manager.is_active(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_special_action<W>(&mut self, action: SpecialAction, mut write_page: W)
|
||||||
|
where
|
||||||
|
W: FnMut(u32, &[u8]) -> Result<(), ()>,
|
||||||
|
{
|
||||||
|
// Execute the requested special action, updating calibration/throttle state as needed.
|
||||||
|
match action {
|
||||||
|
SpecialAction::Bootloader => {}
|
||||||
|
SpecialAction::StartCalibration => {
|
||||||
|
for (index, axis) in self.axis_manager.axes.iter_mut().enumerate() {
|
||||||
|
let centered = self.smoother[index].value() as u16;
|
||||||
|
axis.center = centered;
|
||||||
|
axis.min = centered;
|
||||||
|
axis.max = centered;
|
||||||
|
}
|
||||||
|
self.axis_manager.clear_throttle_hold();
|
||||||
|
self.calibration_manager.start_calibration();
|
||||||
|
}
|
||||||
|
SpecialAction::CancelCalibration => {
|
||||||
|
self.calibration_manager.stop_calibration();
|
||||||
|
}
|
||||||
|
SpecialAction::ThrottleHold(value) => {
|
||||||
|
self.axis_manager.set_throttle_hold(value);
|
||||||
|
}
|
||||||
|
SpecialAction::VirtualThrottleToggle => {
|
||||||
|
self.vt_enable = !self.vt_enable;
|
||||||
|
}
|
||||||
|
SpecialAction::CalibrationSetModeM10 => {
|
||||||
|
if self
|
||||||
|
.calibration_manager
|
||||||
|
.set_gimbal_mode_m10(&mut self.axis_manager.axes, &self.smoother)
|
||||||
|
{
|
||||||
|
self.axis_manager
|
||||||
|
.set_gimbal_mode(self.calibration_manager.get_gimbal_mode());
|
||||||
|
self.axis_manager.clear_throttle_hold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpecialAction::CalibrationSetModeM7 => {
|
||||||
|
if self
|
||||||
|
.calibration_manager
|
||||||
|
.set_gimbal_mode_m7(&mut self.axis_manager.axes, &self.smoother)
|
||||||
|
{
|
||||||
|
self.axis_manager
|
||||||
|
.set_gimbal_mode(self.calibration_manager.get_gimbal_mode());
|
||||||
|
self.axis_manager.clear_throttle_hold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpecialAction::CalibrationSave => {
|
||||||
|
self.calibration_manager
|
||||||
|
.save_calibration(&self.axis_manager.axes, &mut |page, data| {
|
||||||
|
write_page(page, data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SpecialAction::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_calibration_tracking(&mut self) {
|
||||||
|
self.calibration_manager
|
||||||
|
.update_dynamic_calibration(&mut self.axis_manager.axes, &self.smoother);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_smoothers(&mut self, raw: &mut [u16; hardware::NBR_OF_GIMBAL_AXIS]) {
|
||||||
|
self.axis_manager.apply_gimbal_compensation(raw);
|
||||||
|
self.axis_manager.update_smoothers(&mut self.smoother, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_axes(&mut self) -> bool {
|
||||||
|
self.axis_manager
|
||||||
|
.process_axis_values(&self.smoother, &self.expo_primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_virtual_axes(&mut self) -> bool {
|
||||||
|
self.axis_manager
|
||||||
|
.update_virtual_axes(self.button_manager.buttons(), self.vt_enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vt_enable(&self) -> bool {
|
||||||
|
self.vt_enable
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usb_state(&mut self) -> &mut UsbState {
|
||||||
|
&mut self.usb
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn axis_manager(&mut self) -> &mut AxisManager {
|
||||||
|
&mut self.axis_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button_manager(&mut self) -> &mut ButtonManager {
|
||||||
|
&mut self.button_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expo_virtual(&self) -> &ExpoLUT {
|
||||||
|
&self.expo_virtual
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn smoother(&self) -> &[DynamicSmootherEcoI32; hardware::NBR_OF_GIMBAL_AXIS] {
|
||||||
|
&self.smoother
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calibration_manager(&self) -> &CalibrationManager {
|
||||||
|
&self.calibration_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_state(&self) -> SystemState {
|
||||||
|
SystemState {
|
||||||
|
usb_active: self.usb.active,
|
||||||
|
usb_initialized: self.usb.initialized,
|
||||||
|
usb_suspended: self.usb.suspended,
|
||||||
|
idle_mode: self.usb.idle_mode,
|
||||||
|
calibration_active: self.calibration_manager.is_active(),
|
||||||
|
throttle_hold_enable: self.axis_manager.throttle_hold_enable,
|
||||||
|
vt_enable: self.vt_enable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_report(&mut self) -> JoystickReport {
|
||||||
|
let virtual_ry = self.axis_manager.get_virtual_ry_value(&self.expo_virtual);
|
||||||
|
let virtual_rz = self.axis_manager.get_virtual_rz_value(&self.expo_virtual);
|
||||||
|
get_joystick_report(
|
||||||
|
self.button_manager.buttons_mut(),
|
||||||
|
&mut self.axis_manager.axes,
|
||||||
|
virtual_ry,
|
||||||
|
virtual_rz,
|
||||||
|
&self.vt_enable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_report() -> JoystickReport {
|
||||||
|
JoystickReport {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
rz: 0,
|
||||||
|
slider: 0,
|
||||||
|
hat: 8,
|
||||||
|
buttons: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,18 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
//! CMDR Joystick 25 firmware library for RP2040.
|
//! CMDR Joystick firmware library for RP2040.
|
||||||
//!
|
//!
|
||||||
//! This crate provides the reusable building blocks that power the main
|
//! This crate provides the reusable building blocks that power the main
|
||||||
//! firmware: axis processing, button handling, calibration and storage, USB
|
//! firmware: axis processing, button handling, calibration and storage, USB
|
||||||
//! HID reporting, and hardware/status abstractions.
|
//! HID reporting, and hardware/status abstractions.
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub mod board;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub mod bootloader;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub mod joystick;
|
||||||
|
|
||||||
/// Axis processing for gimbal and virtual axes (smoothing, expo, holds).
|
/// Axis processing for gimbal and virtual axes (smoothing, expo, holds).
|
||||||
pub mod axis;
|
pub mod axis;
|
||||||
/// Row/column scanned button matrix driver with debouncing.
|
/// Row/column scanned button matrix driver with debouncing.
|
||||||
@ -29,6 +36,11 @@ pub mod usb_joystick_device;
|
|||||||
/// Convert runtime state into USB HID joystick reports.
|
/// Convert runtime state into USB HID joystick reports.
|
||||||
pub mod usb_report;
|
pub mod usb_report;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use board::{AxisAnalogPins, Board, BoardParts, JoystickMatrix, JoystickStatusLed};
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use joystick::{JoystickState, UsbState};
|
||||||
|
|
||||||
/// Re-exports for convenient access in `main` and downstream consumers.
|
/// Re-exports for convenient access in `main` and downstream consumers.
|
||||||
pub use axis::{AxisManager, GimbalAxis, VirtualAxis};
|
pub use axis::{AxisManager, GimbalAxis, VirtualAxis};
|
||||||
pub use calibration::CalibrationManager;
|
pub use calibration::CalibrationManager;
|
||||||
|
|||||||
@ -1,273 +1,53 @@
|
|||||||
//! CMDR Joystick 25 – RP2040 main firmware
|
//! Project: CMtec CMDR Joystick
|
||||||
//!
|
//! Date: 2025-03-09
|
||||||
//! Overview
|
//! Author: Christoffer Martinsson
|
||||||
//! - 4 gimbal axes (LX, LY, RX, RY) with smoothing, calibration and expo
|
//! Email: cm@cmtec.se
|
||||||
//! - 2 virtual axes (RY/RZ) driven by buttons with direction compensation
|
//! License: Please refer to LICENSE in root directory
|
||||||
//! - 5x5 button matrix + 2 extra buttons, with debounce and short/long press
|
|
||||||
//! - USB HID joystick: 7 axes, 32 buttons, 8‑way HAT
|
|
||||||
//! - EEPROM‑backed calibration and gimbal mode (M10/M7)
|
|
||||||
//! - WS2812 status LED for state indication
|
|
||||||
//!
|
|
||||||
//! Modules
|
|
||||||
//! - hardware.rs: pins, clocks, timers, helpers
|
|
||||||
//! - axis.rs: gimbal/virtual axis processing and throttle hold
|
|
||||||
//! - button_matrix.rs + buttons.rs: scanning, debouncing, press types, special actions
|
|
||||||
//! - calibration.rs + storage.rs: runtime calibration and persistence
|
|
||||||
//! - usb_report.rs + usb_joystick_device.rs: HID descriptor and report generation
|
|
||||||
//! - status.rs: WS2812 driver and status model
|
|
||||||
//!
|
|
||||||
//! Modes: Normal, Calibration, Throttle Hold, Virtual Throttle, Bootloader
|
|
||||||
//!
|
|
||||||
//! Timing: scan 200 µs, process 1200 µs, USB 10 ms, LED 250 ms
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
mod axis;
|
use cmdr_joystick::buttons::SpecialAction;
|
||||||
mod button_matrix;
|
use cmdr_joystick::hardware::{self, timers};
|
||||||
mod buttons;
|
use cmdr_joystick::status::StatusMode;
|
||||||
mod calibration;
|
use cmdr_joystick::usb_joystick_device::JoystickConfig;
|
||||||
mod expo;
|
use cmdr_joystick::{bootloader, Board, BoardParts, JoystickState};
|
||||||
mod hardware;
|
|
||||||
mod mapping;
|
|
||||||
mod status;
|
|
||||||
mod storage;
|
|
||||||
mod usb_joystick_device;
|
|
||||||
mod usb_report;
|
|
||||||
|
|
||||||
use axis::AxisManager;
|
|
||||||
use button_matrix::ButtonMatrix;
|
|
||||||
use buttons::{ButtonManager, SpecialAction};
|
|
||||||
use calibration::CalibrationManager;
|
|
||||||
use core::convert::Infallible;
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
use cortex_m::delay::Delay;
|
|
||||||
use eeprom24x::{Eeprom24x, SlaveAddr};
|
|
||||||
use embedded_hal::digital::{InputPin, OutputPin};
|
|
||||||
use embedded_hal_0_2::adc::OneShot;
|
use embedded_hal_0_2::adc::OneShot;
|
||||||
use embedded_hal_0_2::timer::CountDown;
|
use embedded_hal_0_2::timer::CountDown;
|
||||||
use fugit::ExtU32;
|
use fugit::ExtU32;
|
||||||
use hardware::timers;
|
use panic_halt as _;
|
||||||
use mapping::*;
|
|
||||||
use rp2040_hal::{
|
|
||||||
adc::Adc,
|
|
||||||
adc::AdcPin,
|
|
||||||
clocks::{init_clocks_and_plls, Clock},
|
|
||||||
gpio::Pins,
|
|
||||||
i2c::I2C,
|
|
||||||
pac,
|
|
||||||
pio::PIOExt,
|
|
||||||
timer::Timer,
|
|
||||||
watchdog::Watchdog,
|
|
||||||
Sio,
|
|
||||||
};
|
|
||||||
use status::{StatusLed, StatusMode, SystemState};
|
|
||||||
use usb_device::class_prelude::*;
|
|
||||||
use usb_device::prelude::*;
|
use usb_device::prelude::*;
|
||||||
use usb_device::device::UsbDeviceState;
|
use usbd_human_interface_device::prelude::{UsbHidClassBuilder, UsbHidError};
|
||||||
use usb_joystick_device::JoystickConfig;
|
|
||||||
use usb_report::get_joystick_report;
|
|
||||||
use usbd_human_interface_device::prelude::*;
|
|
||||||
|
|
||||||
#[panic_handler]
|
// Embed the boot2 image for the W25Q080 flash; required for RP2040 to boot from external flash.
|
||||||
fn panic(_info: &PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Boot loader configuration for RP2040 ROM.
|
|
||||||
///
|
|
||||||
/// The linker places this boot block at the start of our program image to help the ROM
|
|
||||||
/// bootloader initialize our code. This specific boot loader supports W25Q080 flash memory.
|
|
||||||
#[link_section = ".boot2"]
|
#[link_section = ".boot2"]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[used]
|
#[used]
|
||||||
pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
|
pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
|
||||||
|
|
||||||
use expo::ExpoLUT;
|
|
||||||
|
|
||||||
/// Hardware configuration imports from the hardware abstraction layer.
|
|
||||||
use hardware::{ADC_MAX, ADC_MIN};
|
|
||||||
use hardware::{BUTTON_COLS, BUTTON_ROWS, NUMBER_OF_BUTTONS};
|
|
||||||
|
|
||||||
/// Additional hardware constants for button debouncing.
|
|
||||||
use hardware::DEBOUNCE;
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
#[rp2040_hal::entry]
|
#[rp2040_hal::entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
// Hardware initialization and peripheral setup for joystick operation
|
// Firmware entry point initializes hardware before handing off to RTIC.
|
||||||
|
let BoardParts {
|
||||||
// Acquire exclusive access to RP2040 peripherals
|
mut button_matrix,
|
||||||
let mut pac = pac::Peripherals::take().unwrap();
|
mut status_led,
|
||||||
|
mut delay,
|
||||||
// Initialize watchdog timer (required for clock configuration)
|
timer,
|
||||||
let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
mut adc,
|
||||||
|
mut axis_pins,
|
||||||
// Configure system clocks and phase-locked loops for stable operation
|
mut left_extra_button,
|
||||||
let clocks = init_clocks_and_plls(
|
mut right_extra_button,
|
||||||
hardware::XTAL_FREQ_HZ,
|
mut eeprom,
|
||||||
pac.XOSC,
|
usb_bus,
|
||||||
pac.CLOCKS,
|
} = Board::new().into_parts();
|
||||||
pac.PLL_SYS,
|
|
||||||
pac.PLL_USB,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
&mut watchdog,
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let core = pac::CorePeripherals::take().unwrap();
|
|
||||||
|
|
||||||
// Initialize SIO (Single-cycle I/O) for high-performance GPIO operations
|
|
||||||
let sio = Sio::new(pac.SIO);
|
|
||||||
|
|
||||||
// Configure GPIO pins to their default operational state
|
|
||||||
let pins = Pins::new(
|
|
||||||
pac.IO_BANK0,
|
|
||||||
pac.PADS_BANK0,
|
|
||||||
sio.gpio_bank0,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
);
|
|
||||||
|
|
||||||
let i2c = I2C::i2c1(
|
|
||||||
pac.I2C1,
|
|
||||||
get_pin!(pins, i2c_sda).reconfigure(), // sda
|
|
||||||
get_pin!(pins, i2c_scl).reconfigure(), // scl
|
|
||||||
hardware::i2c::i2c_frequency(),
|
|
||||||
&mut pac.RESETS,
|
|
||||||
hardware::i2c::system_clock(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let i2c_address = SlaveAddr::Alternative(false, false, false);
|
|
||||||
let mut eeprom = Eeprom24x::new_24x32(i2c, i2c_address);
|
|
||||||
|
|
||||||
// ADC configuration: prepare 12-bit ADC channels for all four gimbal axes
|
|
||||||
|
|
||||||
// Initialize 12-bit ADC with 4 channels for gimbal axes
|
|
||||||
let mut adc = Adc::new(pac.ADC, &mut pac.RESETS);
|
|
||||||
|
|
||||||
// Configure ADC input pins for 4-axis gimbal (Left X/Y, Right X/Y)
|
|
||||||
let mut adc_pin_left_x = AdcPin::new(get_pin!(pins, adc_left_x).into_floating_input()).unwrap();
|
|
||||||
let mut adc_pin_left_y = AdcPin::new(get_pin!(pins, adc_left_y).into_floating_input()).unwrap();
|
|
||||||
let mut adc_pin_right_x =
|
|
||||||
AdcPin::new(get_pin!(pins, adc_right_x).into_floating_input()).unwrap();
|
|
||||||
let mut adc_pin_right_y =
|
|
||||||
AdcPin::new(get_pin!(pins, adc_right_y).into_floating_input()).unwrap();
|
|
||||||
|
|
||||||
// # Button Matrix Configuration\n //\n // Configure the 5x5 button matrix using row/column scanning technique.\n // Rows are configured as pull-up inputs, columns as push-pull outputs.\n // This allows scanning 25 buttons with only 10 GPIO pins.\n\n // Configure button matrix row pins (inputs with pull-up resistors)
|
|
||||||
let button_matrix_row_pins: &mut [&mut dyn InputPin<Error = Infallible>; BUTTON_ROWS] = &mut [
|
|
||||||
&mut get_pin!(pins, button_row_0).into_pull_up_input(),
|
|
||||||
&mut get_pin!(pins, button_row_1).into_pull_up_input(),
|
|
||||||
&mut get_pin!(pins, button_row_2).into_pull_up_input(),
|
|
||||||
&mut get_pin!(pins, button_row_3).into_pull_up_input(),
|
|
||||||
&mut get_pin!(pins, button_row_4).into_pull_up_input(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Configure button matrix column pins (push-pull outputs for scanning)
|
|
||||||
let button_matrix_col_pins: &mut [&mut dyn OutputPin<Error = Infallible>; BUTTON_COLS] = &mut [
|
|
||||||
&mut get_pin!(pins, button_col_0).into_push_pull_output(),
|
|
||||||
&mut get_pin!(pins, button_col_1).into_push_pull_output(),
|
|
||||||
&mut get_pin!(pins, button_col_2).into_push_pull_output(),
|
|
||||||
&mut get_pin!(pins, button_col_3).into_push_pull_output(),
|
|
||||||
&mut get_pin!(pins, button_col_4).into_push_pull_output(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Initialize button matrix scanner with debouncing
|
|
||||||
let mut button_matrix: ButtonMatrix<BUTTON_ROWS, BUTTON_COLS, NUMBER_OF_BUTTONS> =
|
|
||||||
ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, DEBOUNCE);
|
|
||||||
|
|
||||||
// Configure matrix pins for scanning operation
|
|
||||||
button_matrix.init_pins();
|
|
||||||
|
|
||||||
// Configure additional buttons outside the matrix (total: 27 buttons)
|
|
||||||
let mut left_extra_button = get_pin!(pins, left_extra_button).into_pull_up_input();
|
|
||||||
let mut right_extra_button = get_pin!(pins, right_extra_button).into_pull_up_input();
|
|
||||||
|
|
||||||
// Status LED initialization: WS2812 via PIO for runtime status indication
|
|
||||||
|
|
||||||
// Initialize WS2812 status LED using PIO state machine
|
|
||||||
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
|
|
||||||
let mut status_led = StatusLed::new(
|
|
||||||
get_pin!(pins, status_led).into_function(),
|
|
||||||
&mut pio,
|
|
||||||
sm0,
|
|
||||||
clocks.peripheral_clock.freq(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initial LED state (red) indicates system initialization
|
|
||||||
status_led.update(StatusMode::Error);
|
|
||||||
|
|
||||||
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
|
||||||
|
|
||||||
// Bootloader entry check: early matrix scan; hold front‑left‑lower to enter USB mass‑storage bootloader
|
|
||||||
|
|
||||||
// Scan button matrix multiple times to ensure stable debounced readings
|
|
||||||
for _ in 0..10 {
|
|
||||||
// Multiple scans ensure debounce algorithm captures stable button states
|
|
||||||
button_matrix.scan_matrix(&mut delay);
|
|
||||||
}
|
|
||||||
if button_matrix.buttons_pressed()[BUTTON_FRONT_LEFT_LOWER] {
|
|
||||||
status_led.update(StatusMode::Bootloader);
|
|
||||||
let gpio_activity_pin_mask: u32 = 0;
|
|
||||||
let disable_interface_mask: u32 = 0;
|
|
||||||
rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer configuration: cadence for LED updates, scans, processing and USB
|
|
||||||
|
|
||||||
// Initialize hardware timer peripheral
|
|
||||||
let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
|
|
||||||
|
|
||||||
let mut status_led_count_down = timer.count_down();
|
|
||||||
status_led_count_down.start(timers::STATUS_LED_INTERVAL_MS.millis());
|
|
||||||
|
|
||||||
// Removed unused millisecond countdown timer
|
|
||||||
|
|
||||||
let mut scan_count_down = timer.count_down();
|
|
||||||
scan_count_down.start(timers::SCAN_INTERVAL_US.micros());
|
|
||||||
|
|
||||||
let mut usb_update_count_down = timer.count_down();
|
|
||||||
usb_update_count_down.start(timers::USB_UPDATE_INTERVAL_MS.millis());
|
|
||||||
|
|
||||||
let mut usb_activity: bool = false;
|
|
||||||
let mut usb_active: bool = false;
|
|
||||||
let mut usb_initialized: bool = false;
|
|
||||||
let mut usb_suspended: bool = false;
|
|
||||||
let mut usb_send_pending: bool = false;
|
|
||||||
let mut vt_enable: bool = false;
|
|
||||||
let mut idle_mode: bool = false;
|
|
||||||
let mut usb_activity_timeout_count: u32 = 0;
|
|
||||||
let mut wake_on_input: bool = false;
|
|
||||||
|
|
||||||
let mut axis_manager = AxisManager::new();
|
|
||||||
let mut button_manager = ButtonManager::new();
|
|
||||||
let mut calibration_manager = CalibrationManager::new();
|
|
||||||
|
|
||||||
// Signal processing: expo LUTs and smoothing filters
|
|
||||||
|
|
||||||
// Create exponential curve lookup tables (avoids floating-point math in real-time)
|
|
||||||
let expo_lut = ExpoLUT::new(0.3);
|
|
||||||
let expo_lut_virtual = ExpoLUT::new(0.6);
|
|
||||||
|
|
||||||
// Initialize digital smoothing filters for each gimbal axis
|
|
||||||
let mut smoother = AxisManager::create_smoothers();
|
|
||||||
|
|
||||||
// USB HID configuration (full‑speed joystick class)
|
|
||||||
|
|
||||||
// Initialize USB bus allocator for RP2040
|
|
||||||
let usb_bus = UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new(
|
|
||||||
pac.USBCTRL_REGS,
|
|
||||||
pac.USBCTRL_DPRAM,
|
|
||||||
clocks.usb_clock,
|
|
||||||
true,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
));
|
|
||||||
|
|
||||||
|
// Build the HID joystick class on the shared USB bus.
|
||||||
let mut usb_hid_joystick = UsbHidClassBuilder::new()
|
let mut usb_hid_joystick = UsbHidClassBuilder::new()
|
||||||
.add_device(JoystickConfig::default())
|
.add_device(JoystickConfig::default())
|
||||||
.build(&usb_bus);
|
.build(usb_bus);
|
||||||
|
|
||||||
let mut usb_dev =
|
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()
|
.strings(&[StringDescriptors::default()
|
||||||
.manufacturer(hardware::usb::MANUFACTURER)
|
.manufacturer(hardware::usb::MANUFACTURER)
|
||||||
.product(hardware::usb::PRODUCT)
|
.product(hardware::usb::PRODUCT)
|
||||||
@ -275,285 +55,141 @@ fn main() -> ! {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Calibration data initialization: load axis calibration and gimbal mode from EEPROM
|
let mut state = JoystickState::new();
|
||||||
|
status_led.update(StatusMode::Error);
|
||||||
|
|
||||||
// Load calibration data from EEPROM using CalibrationManager
|
button_matrix.prime(&mut delay, 10);
|
||||||
let mut read_fn = |addr: u32| eeprom.read_byte(addr).map_err(|_| ());
|
let initial_pressed = button_matrix.buttons_pressed();
|
||||||
CalibrationManager::load_axis_calibration(&mut axis_manager.axes, &mut read_fn);
|
if bootloader::startup_requested(&initial_pressed) {
|
||||||
let gimbal_mode = CalibrationManager::load_gimbal_mode(&mut read_fn);
|
bootloader::enter(&mut status_led);
|
||||||
axis_manager.set_gimbal_mode(gimbal_mode);
|
}
|
||||||
calibration_manager.set_gimbal_mode(gimbal_mode);
|
|
||||||
|
|
||||||
|
{
|
||||||
|
// Load persisted calibration values from EEPROM if available.
|
||||||
|
let mut read_fn = |addr: u32| eeprom.read_byte(addr).map_err(|_| ());
|
||||||
|
state.load_calibration(&mut read_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up periodic timers for scanning, status updates, and USB activity.
|
||||||
|
let mut scan_tick = timer.count_down();
|
||||||
|
scan_tick.start(timers::SCAN_INTERVAL_US.micros());
|
||||||
|
|
||||||
|
let mut status_tick = timer.count_down();
|
||||||
|
status_tick.start(timers::STATUS_LED_INTERVAL_MS.millis());
|
||||||
|
|
||||||
|
let mut usb_tick = timer.count_down();
|
||||||
|
usb_tick.start(timers::USB_UPDATE_INTERVAL_MS.millis());
|
||||||
|
|
||||||
|
let mut status_time_ms: u32 = 0;
|
||||||
|
let mut suspended_scan_counter: u8 = 0;
|
||||||
|
|
||||||
|
// Main control loop: service USB, process inputs, and emit reports.
|
||||||
loop {
|
loop {
|
||||||
// Main control loop: poll USB, scan inputs, process data, send reports
|
// Service the USB stack and HID class when data is pending.
|
||||||
|
|
||||||
// Handle USB device polling and maintain connection state
|
|
||||||
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
||||||
if !usb_initialized {
|
state.usb_state().on_poll();
|
||||||
usb_initialized = true;
|
|
||||||
}
|
|
||||||
if !usb_active {
|
|
||||||
usb_activity = true; // Force initial report
|
|
||||||
idle_mode = false;
|
|
||||||
usb_activity_timeout_count = 0;
|
|
||||||
usb_send_pending = true;
|
|
||||||
}
|
|
||||||
usb_active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check USB device state for suspend/resume handling
|
|
||||||
let usb_state = usb_dev.state();
|
let usb_state = usb_dev.state();
|
||||||
let was_suspended = usb_suspended;
|
state.usb_state().on_suspend_change(usb_state);
|
||||||
usb_suspended = usb_state == UsbDeviceState::Suspend;
|
|
||||||
|
|
||||||
// Handle USB resume transition
|
// Periodically refresh the status LED animation.
|
||||||
if was_suspended && !usb_suspended {
|
if status_tick.wait().is_ok() {
|
||||||
// Device was suspended and is now resumed
|
status_time_ms = status_time_ms.saturating_add(timers::STATUS_LED_INTERVAL_MS);
|
||||||
usb_activity = true;
|
status_led.update_from_system_state(state.system_state(), status_time_ms);
|
||||||
idle_mode = false;
|
|
||||||
usb_activity_timeout_count = 0;
|
|
||||||
usb_send_pending = true;
|
|
||||||
wake_on_input = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle USB suspend transition
|
// Slow the scan cadence when USB is suspended to save power.
|
||||||
if !was_suspended && usb_suspended {
|
let should_scan = if state.usb_state().suspended {
|
||||||
// Device has just been suspended - enter power saving mode
|
suspended_scan_counter = suspended_scan_counter.wrapping_add(1) % 10;
|
||||||
idle_mode = true;
|
suspended_scan_counter == 0
|
||||||
usb_activity = false;
|
|
||||||
usb_send_pending = false;
|
|
||||||
wake_on_input = true;
|
|
||||||
|
|
||||||
// Reduce LED update frequency to save power when suspended
|
|
||||||
// LED will be off anyway (Suspended mode), so slow updates are fine
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 10 (every ~2ms instead of 200μs)
|
|
||||||
static mut SUSPENDED_SCAN_COUNTER: u8 = 0;
|
|
||||||
unsafe {
|
|
||||||
SUSPENDED_SCAN_COUNTER = (SUSPENDED_SCAN_COUNTER + 1) % 10;
|
|
||||||
SUSPENDED_SCAN_COUNTER == 0
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
suspended_scan_counter = 0;
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
if scan_count_down.wait().is_ok() && should_scan {
|
if should_scan && scan_tick.wait().is_ok() {
|
||||||
// ## High-Frequency Input Sampling (~5 kHz)
|
// Scan buttons, read analog axes, and update state machines.
|
||||||
//
|
|
||||||
// Sample all inputs at high frequency for responsive control:
|
|
||||||
// - Button matrix scanning with debouncing
|
|
||||||
// - ADC reading from all 4 gimbal axes
|
|
||||||
// - Digital filtering for noise reduction
|
|
||||||
|
|
||||||
// Scan 5x5 button matrix for input changes
|
|
||||||
button_matrix.scan_matrix(&mut delay);
|
button_matrix.scan_matrix(&mut delay);
|
||||||
|
|
||||||
// Read raw 12-bit ADC values from all 4 gimbal potentiometers
|
|
||||||
let mut raw_values = [
|
let mut raw_values = [
|
||||||
adc.read(&mut adc_pin_left_x).unwrap(),
|
adc.read(&mut axis_pins.left_x).unwrap(),
|
||||||
adc.read(&mut adc_pin_left_y).unwrap(),
|
adc.read(&mut axis_pins.left_y).unwrap(),
|
||||||
adc.read(&mut adc_pin_right_x).unwrap(),
|
adc.read(&mut axis_pins.right_x).unwrap(),
|
||||||
adc.read(&mut adc_pin_right_y).unwrap(),
|
adc.read(&mut axis_pins.right_y).unwrap(),
|
||||||
];
|
];
|
||||||
|
state.tick_smoothers(&mut raw_values);
|
||||||
|
|
||||||
// Apply hardware-specific axis compensation (M10/M7 differences)
|
state.update_button_states(
|
||||||
axis_manager.apply_gimbal_compensation(&mut raw_values);
|
&mut button_matrix,
|
||||||
|
&mut left_extra_button,
|
||||||
// Apply digital smoothing filters to reduce ADC noise and jitter
|
&mut right_extra_button,
|
||||||
axis_manager.update_smoothers(&mut smoother, &raw_values);
|
|
||||||
|
|
||||||
// ## Immediate Data Processing (formerly 1000 Hz)
|
|
||||||
//
|
|
||||||
// Process all input data right after sampling for minimal latency.
|
|
||||||
|
|
||||||
// Update button states from matrix scan and extra buttons
|
|
||||||
button_manager.update_from_matrix(&mut button_matrix);
|
|
||||||
button_manager.update_extra_buttons(&mut left_extra_button, &mut right_extra_button);
|
|
||||||
button_manager.filter_hat_switches();
|
|
||||||
|
|
||||||
// Process special button combinations for system control
|
|
||||||
let action = button_manager.check_special_combinations(
|
|
||||||
axis_manager.get_value_before_hold(),
|
|
||||||
calibration_manager.is_active(),
|
|
||||||
);
|
);
|
||||||
match action {
|
|
||||||
SpecialAction::Bootloader => {
|
// Evaluate special button combinations (bootloader, calibration, etc.).
|
||||||
status_led.update(StatusMode::Bootloader);
|
let action = state.check_special_action();
|
||||||
let gpio_activity_pin_mask: u32 = 0;
|
if matches!(action, SpecialAction::Bootloader) {
|
||||||
let disable_interface_mask: u32 = 0;
|
if !state.usb_state().suspended {
|
||||||
rp2040_hal::rom_data::reset_to_usb_boot(
|
let clear_report = JoystickState::empty_report();
|
||||||
gpio_activity_pin_mask,
|
for _ in 0..3 {
|
||||||
disable_interface_mask,
|
match usb_hid_joystick.device().write_report(&clear_report) {
|
||||||
);
|
Ok(_) => break,
|
||||||
}
|
Err(UsbHidError::WouldBlock) => {
|
||||||
SpecialAction::StartCalibration => {
|
let _ = usb_hid_joystick.tick();
|
||||||
for (index, item) in axis_manager.axes.iter_mut().enumerate() {
|
}
|
||||||
item.center = smoother[index].value() as u16;
|
Err(_) => break,
|
||||||
item.min = item.center;
|
}
|
||||||
item.max = item.center;
|
|
||||||
}
|
|
||||||
axis_manager.clear_throttle_hold(); // Clear throttle hold when cancelling calibration
|
|
||||||
calibration_manager.start_calibration();
|
|
||||||
}
|
|
||||||
SpecialAction::CancelCalibration => {
|
|
||||||
calibration_manager.stop_calibration();
|
|
||||||
}
|
|
||||||
SpecialAction::ThrottleHold(hold_value) => {
|
|
||||||
axis_manager.set_throttle_hold(hold_value);
|
|
||||||
}
|
|
||||||
SpecialAction::VirtualThrottleToggle => {
|
|
||||||
vt_enable = !vt_enable;
|
|
||||||
}
|
|
||||||
SpecialAction::CalibrationSetModeM10 => {
|
|
||||||
// Set gimbal mode to M10 and reset calibration
|
|
||||||
if calibration_manager.set_gimbal_mode_m10(&mut axis_manager.axes, &smoother) {
|
|
||||||
axis_manager.set_gimbal_mode(calibration_manager.get_gimbal_mode());
|
|
||||||
axis_manager.clear_throttle_hold(); // Clear holds after mode change
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SpecialAction::CalibrationSetModeM7 => {
|
bootloader::enter(&mut status_led);
|
||||||
// Set gimbal mode to M7 and reset calibration
|
} else if !matches!(action, SpecialAction::None) {
|
||||||
if calibration_manager.set_gimbal_mode_m7(&mut axis_manager.axes, &smoother) {
|
let mut write_page =
|
||||||
axis_manager.set_gimbal_mode(calibration_manager.get_gimbal_mode());
|
|page: u32, data: &[u8]| eeprom.write_page(page, data).map_err(|_| ());
|
||||||
axis_manager.clear_throttle_hold(); // Clear holds after mode change
|
state.handle_special_action(action, &mut write_page);
|
||||||
}
|
|
||||||
}
|
|
||||||
SpecialAction::CalibrationSave => {
|
|
||||||
// Save calibration data and end calibration mode
|
|
||||||
calibration_manager
|
|
||||||
.save_calibration(&axis_manager.axes, &mut |page: u32, data: &[u8]| {
|
|
||||||
eeprom.write_page(page, data).map_err(|_| ())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
SpecialAction::None => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always update calibration for dynamic min/max tracking when active
|
// Track calibration extrema and process axis/virtual/button logic.
|
||||||
calibration_manager.update_dynamic_calibration(&mut axis_manager.axes, &smoother);
|
state.update_calibration_tracking();
|
||||||
|
|
||||||
// Process gimbal axes through calibration, expo curves, and scaling
|
if state.process_axes() {
|
||||||
if axis_manager.process_axis_values(&smoother, &expo_lut) {
|
state.usb_state().handle_input_activity();
|
||||||
usb_activity = true;
|
|
||||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
|
||||||
idle_mode = false;
|
|
||||||
usb_send_pending = true;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update virtual axes based on front button states
|
if state.update_virtual_axes() {
|
||||||
if axis_manager.update_virtual_axes(button_manager.buttons(), vt_enable) {
|
state.usb_state().handle_input_activity();
|
||||||
usb_activity = true;
|
|
||||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
|
||||||
idle_mode = false;
|
|
||||||
usb_send_pending = true;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process button logic (press types, timing, USB mapping)
|
if state.finalize_button_logic(&timer) {
|
||||||
if button_manager.process_button_logic_with_timer(&timer) {
|
state.usb_state().handle_input_activity();
|
||||||
usb_activity = true;
|
|
||||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
|
||||||
idle_mode = false;
|
|
||||||
usb_send_pending = true;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status_led_count_down.wait().is_ok() {
|
// Advance USB idle timers and decide when to send reports.
|
||||||
// ## Status LED Updates (100Hz)
|
let usb_tick_elapsed = usb_tick.wait().is_ok();
|
||||||
//
|
if usb_tick_elapsed {
|
||||||
// Update status LED to reflect current system state:
|
state
|
||||||
// - Green: Normal operation with USB connection
|
.usb_state()
|
||||||
// - Blue: Calibration mode active
|
.advance_idle_timer(timers::USB_UPDATE_INTERVAL_MS);
|
||||||
// - Yellow: Throttle hold or Virtual Throttle enabled
|
|
||||||
// - Red: Error state or disconnected
|
|
||||||
// - Purple: Bootloader mode
|
|
||||||
|
|
||||||
let system_state = SystemState {
|
|
||||||
usb_active,
|
|
||||||
usb_initialized,
|
|
||||||
usb_suspended,
|
|
||||||
idle_mode,
|
|
||||||
calibration_active: calibration_manager.is_active(),
|
|
||||||
throttle_hold_enable: axis_manager.throttle_hold_enable,
|
|
||||||
vt_enable,
|
|
||||||
};
|
|
||||||
status_led.update_from_system_state(
|
|
||||||
system_state,
|
|
||||||
(timer.get_counter().ticks() / 1000) as u32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// ## USB HID Report Transmission (up to 1 kHz)
|
|
||||||
//
|
|
||||||
// Transmit USB HID reports only when there is input activity.
|
|
||||||
// This power-management approach prevents the computer from staying
|
|
||||||
// awake unnecessarily while maintaining responsive control.
|
|
||||||
//
|
|
||||||
// The report includes:
|
|
||||||
// - All 7 analog axes with proper scaling
|
|
||||||
// - 32-button bitmask with USB mapping
|
|
||||||
// - 8-direction HAT switch state
|
|
||||||
// - Virtual throttle mode handling
|
|
||||||
|
|
||||||
// Only transmit USB reports when input activity is detected and not suspended
|
// Emit a new HID report when activity is pending and USB is ready.
|
||||||
let usb_tick = usb_update_count_down.wait().is_ok();
|
if state.usb_state().activity
|
||||||
if usb_activity && (usb_tick || usb_send_pending) && !usb_suspended {
|
&& (usb_tick_elapsed || state.usb_state().send_pending)
|
||||||
let mut send_report = || {
|
&& !state.usb_state().suspended
|
||||||
let virtual_ry_value = axis_manager.get_virtual_ry_value(&expo_lut_virtual);
|
{
|
||||||
let virtual_rz_value = axis_manager.get_virtual_rz_value(&expo_lut_virtual);
|
let report = state.build_report();
|
||||||
match usb_hid_joystick.device().write_report(&get_joystick_report(
|
match usb_hid_joystick.device().write_report(&report) {
|
||||||
button_manager.buttons_mut(),
|
Err(UsbHidError::WouldBlock) => {}
|
||||||
&mut axis_manager.axes,
|
Ok(_) => {
|
||||||
virtual_ry_value,
|
state.usb_state().acknowledge_report();
|
||||||
virtual_rz_value,
|
|
||||||
&vt_enable,
|
|
||||||
)) {
|
|
||||||
Err(UsbHidError::WouldBlock) => {}
|
|
||||||
Ok(_) => {
|
|
||||||
usb_send_pending = false;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
status_led.update(StatusMode::Error);
|
|
||||||
core::panic!("Failed to write joystick report: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
Err(error) => {
|
||||||
|
status_led.update(StatusMode::Error);
|
||||||
if usb_tick {
|
panic!("Failed to write joystick report: {:?}", error);
|
||||||
usb_activity_timeout_count += timers::USB_UPDATE_INTERVAL_MS;
|
|
||||||
if usb_activity_timeout_count >= timers::USB_ACTIVITY_TIMEOUT_MS {
|
|
||||||
usb_activity = false;
|
|
||||||
usb_activity_timeout_count = 0;
|
|
||||||
idle_mode = true;
|
|
||||||
usb_send_pending = false;
|
|
||||||
} else {
|
|
||||||
send_report();
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
send_report();
|
|
||||||
}
|
}
|
||||||
} else if usb_tick && usb_active && !usb_suspended {
|
} else if usb_tick_elapsed && state.usb_state().active && !state.usb_state().suspended {
|
||||||
// Only update idle mode for non-suspended devices
|
state.usb_state().idle_mode = true;
|
||||||
idle_mode = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! Button-to-USB mapping for CMDR Joystick 25
|
//! Button-to-USB mapping for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! This module defines the hardware button indices (matrix layout and extras)
|
//! This module defines the hardware button indices (matrix layout and extras)
|
||||||
//! and their mapping to USB joystick button numbers and HAT directions.
|
//! and their mapping to USB joystick button numbers and HAT directions.
|
||||||
@ -171,6 +171,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn front_buttons_have_expected_mappings() {
|
fn front_buttons_have_expected_mappings() {
|
||||||
|
// Front panel buttons map to the expected USB button ids.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
configure_button_mappings(&mut buttons);
|
configure_button_mappings(&mut buttons);
|
||||||
|
|
||||||
@ -181,6 +182,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn long_press_flags_set_correctly() {
|
fn long_press_flags_set_correctly() {
|
||||||
|
// Long-press flags are configured for buttons that need them at runtime.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
configure_button_mappings(&mut buttons);
|
configure_button_mappings(&mut buttons);
|
||||||
|
|
||||||
@ -192,11 +194,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hat_buttons_map_to_expected_ids() {
|
fn hat_buttons_map_to_expected_ids() {
|
||||||
|
// Hat direction buttons should map to the numerical HID hat constants.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
configure_button_mappings(&mut buttons);
|
configure_button_mappings(&mut buttons);
|
||||||
|
|
||||||
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_UP].usb_button, USB_HAT_UP);
|
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_UP].usb_button, USB_HAT_UP);
|
||||||
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_RIGHT].usb_button, USB_HAT_RIGHT);
|
assert_eq!(
|
||||||
|
buttons[BUTTON_TOP_RIGHT_HAT_RIGHT].usb_button,
|
||||||
|
USB_HAT_RIGHT
|
||||||
|
);
|
||||||
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_DOWN].usb_button, USB_HAT_DOWN);
|
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_DOWN].usb_button, USB_HAT_DOWN);
|
||||||
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_LEFT].usb_button, USB_HAT_LEFT);
|
assert_eq!(buttons[BUTTON_TOP_RIGHT_HAT_LEFT].usb_button, USB_HAT_LEFT);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! WS2812 status LED driver for CMDR Joystick 25
|
//! WS2812 status LED driver for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Provides a small helper around `ws2812_pio` to display firmware state on a
|
//! Provides a small helper around `ws2812_pio` to display firmware state on a
|
||||||
//! single WS2812 RGB LED using RP2040 PIO. The driver offers simple modes
|
//! single WS2812 RGB LED using RP2040 PIO. The driver offers simple modes
|
||||||
@ -64,9 +64,11 @@ struct ModeDescriptor {
|
|||||||
|
|
||||||
const HEARTBEAT_POWER_MS: u32 = 800;
|
const HEARTBEAT_POWER_MS: u32 = 800;
|
||||||
const HEARTBEAT_IDLE_MS: u32 = 3200;
|
const HEARTBEAT_IDLE_MS: u32 = 3200;
|
||||||
|
const HEARTBEAT_PAUSE_MS: u32 = 3000;
|
||||||
|
|
||||||
impl LedEffect {
|
impl LedEffect {
|
||||||
fn update_interval_ms(self) -> u32 {
|
fn update_interval_ms(self) -> u32 {
|
||||||
|
// Resolve the LED descriptor for the requested status mode.
|
||||||
match self {
|
match self {
|
||||||
LedEffect::Solid => 0,
|
LedEffect::Solid => 0,
|
||||||
LedEffect::Blink { period_ms } => period_ms / 2,
|
LedEffect::Blink { period_ms } => period_ms / 2,
|
||||||
@ -75,6 +77,7 @@ impl LedEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn color_for(self, base: RGB8, elapsed_ms: u32) -> RGB8 {
|
fn color_for(self, base: RGB8, elapsed_ms: u32) -> RGB8 {
|
||||||
|
// Compute the base status mode given system state flags.
|
||||||
match self {
|
match self {
|
||||||
LedEffect::Solid => base,
|
LedEffect::Solid => base,
|
||||||
LedEffect::Blink { period_ms } => {
|
LedEffect::Blink { period_ms } => {
|
||||||
@ -90,7 +93,12 @@ impl LedEffect {
|
|||||||
}
|
}
|
||||||
LedEffect::Heartbeat { period_ms } => {
|
LedEffect::Heartbeat { period_ms } => {
|
||||||
let period = period_ms.max(1);
|
let period = period_ms.max(1);
|
||||||
let phase = elapsed_ms % period;
|
let cycle = period.saturating_add(HEARTBEAT_PAUSE_MS);
|
||||||
|
let phase = elapsed_ms % cycle;
|
||||||
|
if phase >= period {
|
||||||
|
return COLOR_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
let half = (period / 2).max(1);
|
let half = (period / 2).max(1);
|
||||||
let ramp = if phase < half {
|
let ramp = if phase < half {
|
||||||
((phase * 255) / half) as u8
|
((phase * 255) / half) as u8
|
||||||
@ -197,15 +205,99 @@ const fn descriptor_for(mode: StatusMode, base_mode: StatusMode) -> ModeDescript
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scale_color(base: RGB8, brightness: u8) -> RGB8 {
|
fn scale_color(base: RGB8, brightness: u8) -> RGB8 {
|
||||||
let scale = brightness as u16;
|
if brightness == 0 {
|
||||||
|
return COLOR_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
let components = [base.r, base.g, base.b];
|
||||||
|
let mut scaled = [0u8; 3];
|
||||||
|
let mut remainders = [0u16; 3];
|
||||||
|
let mut total_floor: u16 = 0;
|
||||||
|
let mut total_base: u16 = 0;
|
||||||
|
let brightness_u16 = brightness as u16;
|
||||||
|
|
||||||
|
for (index, &component) in components.iter().enumerate() {
|
||||||
|
total_base += component as u16;
|
||||||
|
if component == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = component as u32 * brightness as u32;
|
||||||
|
let div = (value / 255) as u16;
|
||||||
|
let rem = (value % 255) as u16;
|
||||||
|
|
||||||
|
scaled[index] = div as u8;
|
||||||
|
remainders[index] = rem;
|
||||||
|
total_floor += div;
|
||||||
|
}
|
||||||
|
|
||||||
|
if total_base == 0 {
|
||||||
|
return COLOR_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target_total = ((total_base as u32 * brightness_u16 as u32) + 127) / 255;
|
||||||
|
if target_total > total_base as u32 {
|
||||||
|
target_total = total_base as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut extra = target_total.saturating_sub(total_floor as u32) as u16;
|
||||||
|
|
||||||
|
while extra > 0 {
|
||||||
|
let mut best_index: Option<usize> = None;
|
||||||
|
let mut best_remainder = 0u16;
|
||||||
|
|
||||||
|
for idx in 0..components.len() {
|
||||||
|
if components[idx] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if remainders[idx] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if scaled[idx] as u16 >= components[idx] as u16 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if remainders[idx] > best_remainder {
|
||||||
|
best_remainder = remainders[idx];
|
||||||
|
best_index = Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(idx) = best_index else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
scaled[idx] += 1;
|
||||||
|
remainders[idx] = 0;
|
||||||
|
extra -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if extra > 0 {
|
||||||
|
for idx in 0..components.len() {
|
||||||
|
if extra == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if components[idx] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if scaled[idx] as u16 >= components[idx] as u16 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaled[idx] += 1;
|
||||||
|
extra -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RGB8 {
|
RGB8 {
|
||||||
r: ((base.r as u16 * scale) / 255) as u8,
|
r: scaled[0],
|
||||||
g: ((base.g as u16 * scale) / 255) as u8,
|
g: scaled[1],
|
||||||
b: ((base.b as u16 * scale) / 255) as u8,
|
b: scaled[2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_base_mode(system_state: SystemState) -> StatusMode {
|
fn determine_base_mode(system_state: SystemState) -> StatusMode {
|
||||||
|
// Compute the base status mode based on USB/calibration/idle state.
|
||||||
if system_state.usb_suspended {
|
if system_state.usb_suspended {
|
||||||
StatusMode::Suspended
|
StatusMode::Suspended
|
||||||
} else if system_state.calibration_active {
|
} else if system_state.calibration_active {
|
||||||
@ -331,6 +423,7 @@ where
|
|||||||
|
|
||||||
/// Write a single color to the LED.
|
/// Write a single color to the LED.
|
||||||
fn write_color(&mut self, color: RGB8) {
|
fn write_color(&mut self, color: RGB8) {
|
||||||
|
// Push the color to the WS2812 LED, ignoring transient IO errors.
|
||||||
let _ = self.ws2812_direct.write([color].iter().copied());
|
let _ = self.ws2812_direct.write([color].iter().copied());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,6 +447,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn idle_mode_uses_base_color_with_heartbeat() {
|
fn idle_mode_uses_base_color_with_heartbeat() {
|
||||||
|
// Idle state should inherit the base color while enforcing a heartbeat pattern.
|
||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: false,
|
usb_active: false,
|
||||||
usb_initialized: true,
|
usb_initialized: true,
|
||||||
@ -376,6 +470,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn power_mode_uses_fast_heartbeat() {
|
fn power_mode_uses_fast_heartbeat() {
|
||||||
|
// Power mode descriptor should use the fast heartbeat cadence and green.
|
||||||
let descriptor = descriptor_for(StatusMode::Power, StatusMode::Normal);
|
let descriptor = descriptor_for(StatusMode::Power, StatusMode::Normal);
|
||||||
if let LedEffect::Heartbeat { period_ms } = descriptor.effect {
|
if let LedEffect::Heartbeat { period_ms } = descriptor.effect {
|
||||||
assert_eq!(period_ms, HEARTBEAT_POWER_MS);
|
assert_eq!(period_ms, HEARTBEAT_POWER_MS);
|
||||||
@ -387,6 +482,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn calibration_has_priority_over_idle() {
|
fn calibration_has_priority_over_idle() {
|
||||||
|
// Calibration activity should override idle when both flags are set.
|
||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: true,
|
usb_active: true,
|
||||||
usb_initialized: true,
|
usb_initialized: true,
|
||||||
@ -403,6 +499,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heartbeat_effect_fades() {
|
fn heartbeat_effect_fades() {
|
||||||
|
// Heartbeat should ramp up and down around the target color.
|
||||||
let base = StatusMode::Normal;
|
let base = StatusMode::Normal;
|
||||||
let descriptor = descriptor_for(StatusMode::Idle, base);
|
let descriptor = descriptor_for(StatusMode::Idle, base);
|
||||||
let LedEffect::Heartbeat { period_ms } = descriptor.effect else {
|
let LedEffect::Heartbeat { period_ms } = descriptor.effect else {
|
||||||
@ -420,8 +517,30 @@ mod tests {
|
|||||||
assert_eq!(end.g, 0);
|
assert_eq!(end.g, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heartbeat_pause_keeps_led_off() {
|
||||||
|
// Added pause should hold the LED dark between breaths while allowing a clean restart.
|
||||||
|
let base = StatusMode::Normal;
|
||||||
|
let descriptor = descriptor_for(StatusMode::Idle, base);
|
||||||
|
let LedEffect::Heartbeat { period_ms } = descriptor.effect else {
|
||||||
|
panic!("Idle should use heartbeat effect");
|
||||||
|
};
|
||||||
|
|
||||||
|
let pause_sample = descriptor
|
||||||
|
.effect
|
||||||
|
.color_for(descriptor.color, period_ms + HEARTBEAT_PAUSE_MS / 2);
|
||||||
|
assert_eq!(pause_sample, COLOR_OFF);
|
||||||
|
|
||||||
|
let cycle = period_ms + HEARTBEAT_PAUSE_MS;
|
||||||
|
let restart = descriptor
|
||||||
|
.effect
|
||||||
|
.color_for(descriptor.color, cycle + period_ms / 4);
|
||||||
|
assert!(restart.g > 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn blink_effect_toggles() {
|
fn blink_effect_toggles() {
|
||||||
|
// Blink descriptor should alternate between the color and off state.
|
||||||
let descriptor = descriptor_for(StatusMode::NormalFlash, StatusMode::NormalFlash);
|
let descriptor = descriptor_for(StatusMode::NormalFlash, StatusMode::NormalFlash);
|
||||||
let LedEffect::Blink { period_ms } = descriptor.effect else {
|
let LedEffect::Blink { period_ms } = descriptor.effect else {
|
||||||
panic!("NormalFlash should use blink effect");
|
panic!("NormalFlash should use blink effect");
|
||||||
@ -433,8 +552,30 @@ mod tests {
|
|||||||
assert_eq!(off, COLOR_OFF);
|
assert_eq!(off, COLOR_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn low_brightness_preserves_color_mix() {
|
||||||
|
// Low brightness scaling must not drop any non-zero channel from the blend.
|
||||||
|
let base = COLOR_ORANGE;
|
||||||
|
let dimmed = scale_color(base, 50);
|
||||||
|
assert!(dimmed.r > 0);
|
||||||
|
assert!(dimmed.g > 0);
|
||||||
|
assert_eq!(dimmed.b, 0);
|
||||||
|
assert!(dimmed.r <= base.r);
|
||||||
|
assert!(dimmed.g <= base.g);
|
||||||
|
assert_eq!(dimmed.r as u16 * base.g as u16, dimmed.g as u16 * base.r as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_brightness_turns_off_led() {
|
||||||
|
// Zero brightness should fully blank the LED regardless of the base color.
|
||||||
|
let base = COLOR_BLUE;
|
||||||
|
let off = scale_color(base, 0);
|
||||||
|
assert_eq!(off, COLOR_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn determine_base_mode_before_usb() {
|
fn determine_base_mode_before_usb() {
|
||||||
|
// Before USB comes up the controller should stay in Power mode.
|
||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: false,
|
usb_active: false,
|
||||||
usb_initialized: false,
|
usb_initialized: false,
|
||||||
@ -450,6 +591,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn usb_suspend_takes_priority() {
|
fn usb_suspend_takes_priority() {
|
||||||
|
// USB suspend should trump other status priorities.
|
||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: true,
|
usb_active: true,
|
||||||
usb_initialized: true,
|
usb_initialized: true,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! EEPROM storage for CMDR Joystick 25
|
//! EEPROM storage for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Provides helpers to read/write per‑axis calibration and gimbal mode to an
|
//! Provides helpers to read/write per‑axis calibration and gimbal mode to an
|
||||||
//! external 24x‑series EEPROM. The API is closure‑based to allow testing on
|
//! external 24x‑series EEPROM. The API is closure‑based to allow testing on
|
||||||
@ -79,6 +79,7 @@ pub fn write_calibration_data(
|
|||||||
|
|
||||||
/// Read a u16 value from EEPROM in little‑endian (low then high byte) format.
|
/// Read a u16 value from EEPROM in little‑endian (low then high byte) format.
|
||||||
fn read_u16_with_closure(
|
fn read_u16_with_closure(
|
||||||
|
// Fetch a little-endian u16 by reading two consecutive EEPROM bytes.
|
||||||
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
||||||
low_addr: u32,
|
low_addr: u32,
|
||||||
high_addr: u32,
|
high_addr: u32,
|
||||||
@ -171,6 +172,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_boundary_values() {
|
fn test_boundary_values() {
|
||||||
|
// Pack axis tuples into EEPROM layout and write the gimbal mode byte.
|
||||||
let mut buffer = [0u8; 4];
|
let mut buffer = [0u8; 4];
|
||||||
|
|
||||||
// Test minimum value (manual packing)
|
// Test minimum value (manual packing)
|
||||||
|
|||||||
@ -37,6 +37,7 @@ pub trait Try {
|
|||||||
type Ok;
|
type Ok;
|
||||||
type Error;
|
type Error;
|
||||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||||
|
// Trait shim replicating `core::Try` for use in no_std contexts.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Try for Option<T> {
|
impl<T> Try for Option<T> {
|
||||||
@ -45,6 +46,7 @@ impl<T> Try for Option<T> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_result(self) -> Result<T, NoneError> {
|
fn into_result(self) -> Result<T, NoneError> {
|
||||||
|
// Convert an optional value into a result with a unit error.
|
||||||
self.ok_or(NoneError)
|
self.ok_or(NoneError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,6 +57,7 @@ impl<T, E> Try for Result<T, E> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_result(self) -> Self {
|
fn into_result(self) -> Self {
|
||||||
|
// `Result` already matches the desired signature; return it untouched.
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,12 +168,16 @@ impl<'a, B: UsbBus> DeviceClass<'a> for Joystick<'a, B> {
|
|||||||
type I = Interface<'a, B, InBytes32, OutNone, ReportSingle>;
|
type I = Interface<'a, B, InBytes32, OutNone, ReportSingle>;
|
||||||
|
|
||||||
fn interface(&mut self) -> &mut Self::I {
|
fn interface(&mut self) -> &mut Self::I {
|
||||||
|
// Expose the HID interface so the USB stack can enqueue reports.
|
||||||
&mut self.interface
|
&mut self.interface
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {}
|
fn reset(&mut self) {
|
||||||
|
// Nothing to reset for this simple HID device.
|
||||||
|
}
|
||||||
|
|
||||||
fn tick(&mut self) -> Result<(), UsbHidError> {
|
fn tick(&mut self) -> Result<(), UsbHidError> {
|
||||||
|
// Flush pending HID data and poll the USB stack for new requests.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,6 +188,7 @@ pub struct JoystickConfig<'a> {
|
|||||||
|
|
||||||
impl Default for JoystickConfig<'_> {
|
impl Default for JoystickConfig<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// Construct the HID interface with the default joystick descriptor and endpoints.
|
||||||
Self::new(
|
Self::new(
|
||||||
unwrap!(unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR))
|
unwrap!(unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR))
|
||||||
.boot_device(InterfaceProtocol::None)
|
.boot_device(InterfaceProtocol::None)
|
||||||
@ -203,6 +211,7 @@ impl<'a, B: UsbBus + 'a> UsbAllocatable<'a, B> for JoystickConfig<'a> {
|
|||||||
type Allocated = Joystick<'a, B>;
|
type Allocated = Joystick<'a, B>;
|
||||||
|
|
||||||
fn allocate(self, usb_alloc: &'a UsbBusAllocator<B>) -> Self::Allocated {
|
fn allocate(self, usb_alloc: &'a UsbBusAllocator<B>) -> Self::Allocated {
|
||||||
|
// Allocate the HID interface using the provided USB bus allocator.
|
||||||
Self::Allocated {
|
Self::Allocated {
|
||||||
interface: Interface::new(usb_alloc, self.interface),
|
interface: Interface::new(usb_alloc, self.interface),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! USB HID report generation for CMDR Joystick 25
|
//! USB HID report generation for CMDR Joystick
|
||||||
//!
|
//!
|
||||||
//! Converts processed axis values and button states into a `JoystickReport`
|
//! Converts processed axis values and button states into a `JoystickReport`
|
||||||
//! that matches the HID descriptor defined in `usb_joystick_device.rs`.
|
//! that matches the HID descriptor defined in `usb_joystick_device.rs`.
|
||||||
@ -184,6 +184,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_joystick_report_basic_axes() {
|
fn test_joystick_report_basic_axes() {
|
||||||
|
// Remap helper scales values between integer ranges with clamping.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -214,6 +215,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_throttle_mode() {
|
fn test_virtual_throttle_mode() {
|
||||||
|
// Slider combines throttle hold and virtual throttle for report serialization.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -237,6 +239,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_throttle_below_center() {
|
fn test_virtual_throttle_below_center() {
|
||||||
|
// When VT mode is enabled below center, slider should invert along the left half of travel.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -261,6 +264,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_mapping_regular_buttons() {
|
fn test_button_mapping_regular_buttons() {
|
||||||
|
// Regular buttons should set their HID bitmask without disturbing hat state.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -285,6 +289,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hat_switch_mapping() {
|
fn test_hat_switch_mapping() {
|
||||||
|
// A single hat direction should map to the appropriate HID hat value.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -307,6 +312,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_long_press_button_handling() {
|
fn test_long_press_button_handling() {
|
||||||
|
// Buttons configured for long press should surface the alternate USB id.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -327,6 +333,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_usb_changed_flag_reset() {
|
fn test_usb_changed_flag_reset() {
|
||||||
|
// Packaging a report should clear the usb_changed flags once consumed.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -347,6 +354,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edge_case_hat_values() {
|
fn test_edge_case_hat_values() {
|
||||||
|
// Additional hat directions should map to the correct encoded value.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
@ -368,6 +376,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_buttons_and_hat() {
|
fn test_multiple_buttons_and_hat() {
|
||||||
|
// Report should accommodate simultaneous button presses and hat direction.
|
||||||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||||||
let mut axes = [GimbalAxis::new(); 4];
|
let mut axes = [GimbalAxis::new(); 4];
|
||||||
|
|
||||||
|
|||||||
110
tools/copy_uf2.py
Executable file
110
tools/copy_uf2.py
Executable file
@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Copy a UF2 artifact to a detected RP2040 mass-storage mount."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
path = Path(explicit)
|
||||||
|
return path if path.exists() and path.is_dir() else None
|
||||||
|
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
|
||||||
|
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