Included pin subcommand with auditing and assignment
This commit is contained in:
@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
pub mod presets;
|
||||
pub mod pinmap;
|
||||
|
||||
/// Information about a detected serial port.
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
618
src/board/pinmap.rs
Normal file
618
src/board/pinmap.rs
Normal file
@@ -0,0 +1,618 @@
|
||||
/// Pin maps for supported Arduino boards.
|
||||
///
|
||||
/// Each board has a static pin map describing:
|
||||
/// - Available pins and their capabilities
|
||||
/// - Hardware bus groups (SPI, I2C, UART)
|
||||
/// - Supported capability categories for filtering
|
||||
|
||||
// -- Capability names (used as filter keys) -------------------------------
|
||||
|
||||
/// All recognized pin capabilities. Displayed by `anvil pin --capabilities`.
|
||||
pub const ALL_CAPABILITIES: &[CapabilityInfo] = &[
|
||||
CapabilityInfo { name: "digital", description: "Digital I/O (HIGH/LOW)" },
|
||||
CapabilityInfo { name: "analog", description: "Analog input (ADC)" },
|
||||
CapabilityInfo { name: "pwm", description: "PWM output (analogWrite)" },
|
||||
CapabilityInfo { name: "interrupt", description: "External interrupt (attachInterrupt)" },
|
||||
CapabilityInfo { name: "spi", description: "SPI bus pins" },
|
||||
CapabilityInfo { name: "i2c", description: "I2C / TWI bus pins" },
|
||||
CapabilityInfo { name: "uart", description: "UART / Serial pins" },
|
||||
CapabilityInfo { name: "led", description: "Built-in LED" },
|
||||
];
|
||||
|
||||
/// All recognized pin modes for assignments.
|
||||
pub const ALL_MODES: &[&str] = &[
|
||||
"input", "output", "input_pullup", "pwm", "analog",
|
||||
];
|
||||
|
||||
// -- Data types -----------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CapabilityInfo {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PinInfo {
|
||||
/// Arduino digital pin number
|
||||
pub number: u8,
|
||||
/// Alternate names (e.g. "A0", "LED_BUILTIN", "SDA")
|
||||
pub aliases: &'static [&'static str],
|
||||
/// Capabilities this pin supports
|
||||
pub capabilities: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BusGroup {
|
||||
/// Bus name: "spi", "i2c", "uart", "uart1", etc.
|
||||
pub name: &'static str,
|
||||
/// Human description
|
||||
pub description: &'static str,
|
||||
/// Fixed pins: (role, pin_number)
|
||||
pub fixed_pins: &'static [(&'static str, u8)],
|
||||
/// Roles the user must select a pin for (e.g. "cs" for SPI)
|
||||
pub user_selectable: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoardPinMap {
|
||||
/// Preset board name (matches presets.rs)
|
||||
pub board: &'static str,
|
||||
/// Total digital pin count
|
||||
pub total_digital: u8,
|
||||
/// Total analog-capable pin count
|
||||
pub total_analog: u8,
|
||||
/// All pins
|
||||
pub pins: &'static [PinInfo],
|
||||
/// Hardware bus groups
|
||||
pub groups: &'static [BusGroup],
|
||||
}
|
||||
|
||||
// -- Pin maps -------------------------------------------------------------
|
||||
|
||||
pub static PINMAPS: &[&BoardPinMap] = &[
|
||||
&PIN_MAP_UNO,
|
||||
&PIN_MAP_MEGA,
|
||||
&PIN_MAP_NANO,
|
||||
&PIN_MAP_LEONARDO,
|
||||
&PIN_MAP_MICRO,
|
||||
];
|
||||
|
||||
// Arduino Uno (ATmega328P)
|
||||
static PIN_MAP_UNO: BoardPinMap = BoardPinMap {
|
||||
board: "uno",
|
||||
total_digital: 14,
|
||||
total_analog: 6,
|
||||
pins: &[
|
||||
PinInfo { number: 0, aliases: &["RX"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 1, aliases: &["TX"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 2, aliases: &["INT0"], capabilities: &["digital", "interrupt"] },
|
||||
PinInfo { number: 3, aliases: &["INT1"], capabilities: &["digital", "pwm", "interrupt"] },
|
||||
PinInfo { number: 4, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 5, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 6, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 7, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 8, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 9, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 10, aliases: &["SPI_SS"], capabilities: &["digital", "pwm", "spi"] },
|
||||
PinInfo { number: 11, aliases: &["SPI_MOSI"], capabilities: &["digital", "pwm", "spi"] },
|
||||
PinInfo { number: 12, aliases: &["SPI_MISO"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 13, aliases: &["SPI_SCK", "LED_BUILTIN"], capabilities: &["digital", "spi", "led"] },
|
||||
PinInfo { number: 14, aliases: &["A0"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 15, aliases: &["A1"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 16, aliases: &["A2"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 17, aliases: &["A3"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 18, aliases: &["A4", "SDA"], capabilities: &["digital", "analog", "i2c"] },
|
||||
PinInfo { number: 19, aliases: &["A5", "SCL"], capabilities: &["digital", "analog", "i2c"] },
|
||||
],
|
||||
groups: &[
|
||||
BusGroup {
|
||||
name: "spi",
|
||||
description: "SPI bus (hardware)",
|
||||
fixed_pins: &[("sck", 13), ("mosi", 11), ("miso", 12)],
|
||||
user_selectable: &["cs"],
|
||||
},
|
||||
BusGroup {
|
||||
name: "i2c",
|
||||
description: "I2C / TWI bus",
|
||||
fixed_pins: &[("sda", 18), ("scl", 19)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart",
|
||||
description: "Hardware serial (Serial)",
|
||||
fixed_pins: &[("rx", 0), ("tx", 1)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Arduino Mega 2560
|
||||
static PIN_MAP_MEGA: BoardPinMap = BoardPinMap {
|
||||
board: "mega",
|
||||
total_digital: 54,
|
||||
total_analog: 16,
|
||||
pins: &[
|
||||
PinInfo { number: 0, aliases: &["RX0"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 1, aliases: &["TX0"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 2, aliases: &["INT0"], capabilities: &["digital", "pwm", "interrupt"] },
|
||||
PinInfo { number: 3, aliases: &["INT1"], capabilities: &["digital", "pwm", "interrupt"] },
|
||||
PinInfo { number: 4, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 5, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 6, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 7, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 8, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 9, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 10, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 11, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 12, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 13, aliases: &["LED_BUILTIN"], capabilities: &["digital", "pwm", "led"] },
|
||||
PinInfo { number: 14, aliases: &["TX3"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 15, aliases: &["RX3"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 16, aliases: &["TX2"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 17, aliases: &["RX2"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 18, aliases: &["TX1", "INT5"], capabilities: &["digital", "uart", "interrupt"] },
|
||||
PinInfo { number: 19, aliases: &["RX1", "INT4"], capabilities: &["digital", "uart", "interrupt"] },
|
||||
PinInfo { number: 20, aliases: &["SDA", "INT3"], capabilities: &["digital", "i2c", "interrupt"] },
|
||||
PinInfo { number: 21, aliases: &["SCL", "INT2"], capabilities: &["digital", "i2c", "interrupt"] },
|
||||
// 22-43: general digital I/O
|
||||
PinInfo { number: 22, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 23, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 24, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 25, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 26, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 27, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 28, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 29, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 30, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 31, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 32, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 33, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 34, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 35, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 36, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 37, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 38, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 39, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 40, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 41, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 42, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 43, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 44, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 45, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 46, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 47, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 48, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 49, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 50, aliases: &["SPI_MISO"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 51, aliases: &["SPI_MOSI"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 52, aliases: &["SPI_SCK"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 53, aliases: &["SPI_SS"], capabilities: &["digital", "spi"] },
|
||||
// Analog pins
|
||||
PinInfo { number: 54, aliases: &["A0"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 55, aliases: &["A1"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 56, aliases: &["A2"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 57, aliases: &["A3"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 58, aliases: &["A4"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 59, aliases: &["A5"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 60, aliases: &["A6"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 61, aliases: &["A7"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 62, aliases: &["A8"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 63, aliases: &["A9"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 64, aliases: &["A10"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 65, aliases: &["A11"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 66, aliases: &["A12"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 67, aliases: &["A13"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 68, aliases: &["A14"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 69, aliases: &["A15"], capabilities: &["digital", "analog"] },
|
||||
],
|
||||
groups: &[
|
||||
BusGroup {
|
||||
name: "spi",
|
||||
description: "SPI bus (hardware)",
|
||||
fixed_pins: &[("sck", 52), ("mosi", 51), ("miso", 50)],
|
||||
user_selectable: &["cs"],
|
||||
},
|
||||
BusGroup {
|
||||
name: "i2c",
|
||||
description: "I2C / TWI bus",
|
||||
fixed_pins: &[("sda", 20), ("scl", 21)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart",
|
||||
description: "Hardware serial (Serial)",
|
||||
fixed_pins: &[("rx", 0), ("tx", 1)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart1",
|
||||
description: "Serial1",
|
||||
fixed_pins: &[("rx", 19), ("tx", 18)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart2",
|
||||
description: "Serial2",
|
||||
fixed_pins: &[("rx", 17), ("tx", 16)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart3",
|
||||
description: "Serial3",
|
||||
fixed_pins: &[("rx", 15), ("tx", 14)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Arduino Nano (ATmega328P) -- same silicon as Uno, A6/A7 are analog-only
|
||||
static PIN_MAP_NANO: BoardPinMap = BoardPinMap {
|
||||
board: "nano",
|
||||
total_digital: 14,
|
||||
total_analog: 8,
|
||||
pins: &[
|
||||
PinInfo { number: 0, aliases: &["RX"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 1, aliases: &["TX"], capabilities: &["digital", "uart"] },
|
||||
PinInfo { number: 2, aliases: &["INT0"], capabilities: &["digital", "interrupt"] },
|
||||
PinInfo { number: 3, aliases: &["INT1"], capabilities: &["digital", "pwm", "interrupt"] },
|
||||
PinInfo { number: 4, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 5, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 6, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 7, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 8, aliases: &[], capabilities: &["digital"] },
|
||||
PinInfo { number: 9, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 10, aliases: &["SPI_SS"], capabilities: &["digital", "pwm", "spi"] },
|
||||
PinInfo { number: 11, aliases: &["SPI_MOSI"], capabilities: &["digital", "pwm", "spi"] },
|
||||
PinInfo { number: 12, aliases: &["SPI_MISO"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 13, aliases: &["SPI_SCK", "LED_BUILTIN"], capabilities: &["digital", "spi", "led"] },
|
||||
PinInfo { number: 14, aliases: &["A0"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 15, aliases: &["A1"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 16, aliases: &["A2"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 17, aliases: &["A3"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 18, aliases: &["A4", "SDA"], capabilities: &["digital", "analog", "i2c"] },
|
||||
PinInfo { number: 19, aliases: &["A5", "SCL"], capabilities: &["digital", "analog", "i2c"] },
|
||||
// A6 and A7 are analog-only on Nano (no digital I/O)
|
||||
PinInfo { number: 20, aliases: &["A6"], capabilities: &["analog"] },
|
||||
PinInfo { number: 21, aliases: &["A7"], capabilities: &["analog"] },
|
||||
],
|
||||
groups: &[
|
||||
BusGroup {
|
||||
name: "spi",
|
||||
description: "SPI bus (hardware)",
|
||||
fixed_pins: &[("sck", 13), ("mosi", 11), ("miso", 12)],
|
||||
user_selectable: &["cs"],
|
||||
},
|
||||
BusGroup {
|
||||
name: "i2c",
|
||||
description: "I2C / TWI bus",
|
||||
fixed_pins: &[("sda", 18), ("scl", 19)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart",
|
||||
description: "Hardware serial (Serial)",
|
||||
fixed_pins: &[("rx", 0), ("tx", 1)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Shared pins/groups for ATmega32U4 boards (Leonardo, Micro)
|
||||
static ATM32U4_PINS: &[PinInfo] = &[
|
||||
PinInfo { number: 0, aliases: &["RX", "INT2"], capabilities: &["digital", "uart", "interrupt"] },
|
||||
PinInfo { number: 1, aliases: &["TX", "INT3"], capabilities: &["digital", "uart", "interrupt"] },
|
||||
PinInfo { number: 2, aliases: &["SDA", "INT1"], capabilities: &["digital", "i2c", "interrupt"] },
|
||||
PinInfo { number: 3, aliases: &["SCL", "INT0"], capabilities: &["digital", "pwm", "i2c", "interrupt"] },
|
||||
PinInfo { number: 4, aliases: &["A6"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 5, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 6, aliases: &["A7"], capabilities: &["digital", "pwm", "analog"] },
|
||||
PinInfo { number: 7, aliases: &["INT6"], capabilities: &["digital", "interrupt"] },
|
||||
PinInfo { number: 8, aliases: &["A8"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 9, aliases: &["A9"], capabilities: &["digital", "pwm", "analog"] },
|
||||
PinInfo { number: 10, aliases: &["A10"], capabilities: &["digital", "pwm", "analog"] },
|
||||
PinInfo { number: 11, aliases: &[], capabilities: &["digital", "pwm"] },
|
||||
PinInfo { number: 12, aliases: &["A11"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 13, aliases: &["LED_BUILTIN"], capabilities: &["digital", "pwm", "led"] },
|
||||
// ICSP-routed SPI: MISO=14, SCK=15, MOSI=16, SS=17
|
||||
PinInfo { number: 14, aliases: &["SPI_MISO"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 15, aliases: &["SPI_SCK"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 16, aliases: &["SPI_MOSI"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 17, aliases: &["SPI_SS"], capabilities: &["digital", "spi"] },
|
||||
PinInfo { number: 18, aliases: &["A0"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 19, aliases: &["A1"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 20, aliases: &["A2"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 21, aliases: &["A3"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 22, aliases: &["A4"], capabilities: &["digital", "analog"] },
|
||||
PinInfo { number: 23, aliases: &["A5"], capabilities: &["digital", "analog"] },
|
||||
];
|
||||
|
||||
static ATM32U4_GROUPS: &[BusGroup] = &[
|
||||
BusGroup {
|
||||
name: "spi",
|
||||
description: "SPI bus (ICSP header)",
|
||||
fixed_pins: &[("sck", 15), ("mosi", 16), ("miso", 14)],
|
||||
user_selectable: &["cs"],
|
||||
},
|
||||
BusGroup {
|
||||
name: "i2c",
|
||||
description: "I2C / TWI bus",
|
||||
fixed_pins: &[("sda", 2), ("scl", 3)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
BusGroup {
|
||||
name: "uart",
|
||||
description: "Hardware serial (Serial1)",
|
||||
fixed_pins: &[("rx", 0), ("tx", 1)],
|
||||
user_selectable: &[],
|
||||
},
|
||||
];
|
||||
|
||||
// Arduino Leonardo (ATmega32U4)
|
||||
static PIN_MAP_LEONARDO: BoardPinMap = BoardPinMap {
|
||||
board: "leonardo",
|
||||
total_digital: 20,
|
||||
total_analog: 12,
|
||||
pins: ATM32U4_PINS,
|
||||
groups: ATM32U4_GROUPS,
|
||||
};
|
||||
|
||||
// Arduino Micro (ATmega32U4) -- same silicon as Leonardo
|
||||
static PIN_MAP_MICRO: BoardPinMap = BoardPinMap {
|
||||
board: "micro",
|
||||
total_digital: 20,
|
||||
total_analog: 12,
|
||||
pins: ATM32U4_PINS,
|
||||
groups: ATM32U4_GROUPS,
|
||||
};
|
||||
|
||||
// -- Lookup ---------------------------------------------------------------
|
||||
|
||||
/// Find the pin map for a board by preset name.
|
||||
pub fn find_pinmap(board: &str) -> Option<&'static BoardPinMap> {
|
||||
let lower = board.to_lowercase();
|
||||
PINMAPS.iter().find(|m| m.board == lower).copied()
|
||||
}
|
||||
|
||||
/// Find the pin map for a board, also checking for nano-old -> nano alias.
|
||||
pub fn find_pinmap_fuzzy(board: &str) -> Option<&'static BoardPinMap> {
|
||||
find_pinmap(board).or_else(|| {
|
||||
// nano-old uses the same pin map as nano
|
||||
if board == "nano-old" {
|
||||
find_pinmap("nano")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a pin number is valid for a given board.
|
||||
pub fn is_valid_pin(pinmap: &BoardPinMap, pin: u8) -> bool {
|
||||
pinmap.pins.iter().any(|p| p.number == pin)
|
||||
}
|
||||
|
||||
/// Get info about a specific pin.
|
||||
pub fn get_pin(pinmap: &BoardPinMap, pin: u8) -> Option<&PinInfo> {
|
||||
pinmap.pins.iter().find(|p| p.number == pin)
|
||||
}
|
||||
|
||||
/// Resolve an alias like "A0", "SDA", "LED_BUILTIN" to a pin number.
|
||||
pub fn resolve_alias(pinmap: &BoardPinMap, name: &str) -> Option<u8> {
|
||||
let upper = name.to_uppercase();
|
||||
// Try as a number first
|
||||
if let Ok(n) = name.parse::<u8>() {
|
||||
if is_valid_pin(pinmap, n) {
|
||||
return Some(n);
|
||||
}
|
||||
}
|
||||
// Try aliases
|
||||
for p in pinmap.pins {
|
||||
for alias in p.aliases {
|
||||
if alias.to_uppercase() == upper {
|
||||
return Some(p.number);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get all pins that have a given capability.
|
||||
pub fn pins_with_capability<'a>(pinmap: &'a BoardPinMap, capability: &str) -> Vec<&'a PinInfo> {
|
||||
pinmap.pins.iter()
|
||||
.filter(|p| p.capabilities.contains(&capability))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all capabilities present on this board (deduplicated).
|
||||
pub fn board_capabilities(pinmap: &BoardPinMap) -> Vec<&'static str> {
|
||||
let mut caps: Vec<&str> = Vec::new();
|
||||
for p in pinmap.pins {
|
||||
for &c in p.capabilities {
|
||||
if !caps.contains(&c) {
|
||||
caps.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
caps.sort();
|
||||
caps
|
||||
}
|
||||
|
||||
/// Get all pins reserved by a bus group (fixed + any user-provided).
|
||||
pub fn bus_fixed_pins(group: &BusGroup) -> Vec<(String, u8)> {
|
||||
group.fixed_pins.iter()
|
||||
.map(|&(role, pin)| (role.to_string(), pin))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List all board names that have pin maps.
|
||||
pub fn available_boards() -> Vec<&'static str> {
|
||||
PINMAPS.iter().map(|m| m.board).collect()
|
||||
}
|
||||
|
||||
// -- Tests ----------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_find_pinmap_uno() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(m.board, "uno");
|
||||
assert_eq!(m.total_digital, 14);
|
||||
assert_eq!(m.total_analog, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_pinmap_mega() {
|
||||
let m = find_pinmap("mega").unwrap();
|
||||
assert_eq!(m.board, "mega");
|
||||
assert_eq!(m.total_digital, 54);
|
||||
assert_eq!(m.total_analog, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_pinmap_case_insensitive() {
|
||||
// find_pinmap lowercases internally
|
||||
assert!(find_pinmap("UNO").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_pinmap_unknown() {
|
||||
assert!(find_pinmap("esp32").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzy_nano_old() {
|
||||
assert!(find_pinmap_fuzzy("nano-old").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_alias_number() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(resolve_alias(m, "13"), Some(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_alias_a0() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(resolve_alias(m, "A0"), Some(14));
|
||||
assert_eq!(resolve_alias(m, "a0"), Some(14));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_alias_sda() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(resolve_alias(m, "SDA"), Some(18));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_alias_led_builtin() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(resolve_alias(m, "LED_BUILTIN"), Some(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_alias_invalid() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
assert_eq!(resolve_alias(m, "NOPE"), None);
|
||||
assert_eq!(resolve_alias(m, "99"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pins_with_capability_pwm() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
let pwm = pins_with_capability(m, "pwm");
|
||||
let nums: Vec<u8> = pwm.iter().map(|p| p.number).collect();
|
||||
assert_eq!(nums, vec![3, 5, 6, 9, 10, 11]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pins_with_capability_analog() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
let analog = pins_with_capability(m, "analog");
|
||||
assert_eq!(analog.len(), 6); // A0-A5
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_board_capabilities() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
let caps = board_capabilities(m);
|
||||
assert!(caps.contains(&"digital"));
|
||||
assert!(caps.contains(&"pwm"));
|
||||
assert!(caps.contains(&"analog"));
|
||||
assert!(caps.contains(&"spi"));
|
||||
assert!(caps.contains(&"i2c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mega_spi_different_from_uno() {
|
||||
let uno = find_pinmap("uno").unwrap();
|
||||
let mega = find_pinmap("mega").unwrap();
|
||||
let uno_spi = uno.groups.iter().find(|g| g.name == "spi").unwrap();
|
||||
let mega_spi = mega.groups.iter().find(|g| g.name == "spi").unwrap();
|
||||
// Uno SPI SCK = 13, Mega SPI SCK = 52
|
||||
let uno_sck = uno_spi.fixed_pins.iter().find(|p| p.0 == "sck").unwrap().1;
|
||||
let mega_sck = mega_spi.fixed_pins.iter().find(|p| p.0 == "sck").unwrap().1;
|
||||
assert_ne!(uno_sck, mega_sck);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mega_has_multiple_uarts() {
|
||||
let m = find_pinmap("mega").unwrap();
|
||||
let uarts: Vec<_> = m.groups.iter()
|
||||
.filter(|g| g.name.starts_with("uart"))
|
||||
.collect();
|
||||
assert_eq!(uarts.len(), 4); // uart, uart1, uart2, uart3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leonardo_i2c_on_2_3() {
|
||||
let m = find_pinmap("leonardo").unwrap();
|
||||
let i2c = m.groups.iter().find(|g| g.name == "i2c").unwrap();
|
||||
let sda = i2c.fixed_pins.iter().find(|p| p.0 == "sda").unwrap().1;
|
||||
let scl = i2c.fixed_pins.iter().find(|p| p.0 == "scl").unwrap().1;
|
||||
assert_eq!(sda, 2);
|
||||
assert_eq!(scl, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_micro_shares_leonardo_pins() {
|
||||
let leo = find_pinmap("leonardo").unwrap();
|
||||
let mic = find_pinmap("micro").unwrap();
|
||||
assert_eq!(leo.pins.len(), mic.pins.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nano_has_analog_only_pins() {
|
||||
let m = find_pinmap("nano").unwrap();
|
||||
let a6 = get_pin(m, 20).unwrap();
|
||||
assert!(a6.aliases.contains(&"A6"));
|
||||
assert!(a6.capabilities.contains(&"analog"));
|
||||
assert!(!a6.capabilities.contains(&"digital"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spi_has_user_selectable_cs() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
let spi = m.groups.iter().find(|g| g.name == "spi").unwrap();
|
||||
assert!(spi.user_selectable.contains(&"cs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i2c_has_no_user_selectable() {
|
||||
let m = find_pinmap("uno").unwrap();
|
||||
let i2c = m.groups.iter().find(|g| g.name == "i2c").unwrap();
|
||||
assert!(i2c.user_selectable.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_available_boards() {
|
||||
let boards = available_boards();
|
||||
assert!(boards.contains(&"uno"));
|
||||
assert!(boards.contains(&"mega"));
|
||||
assert!(boards.contains(&"nano"));
|
||||
assert!(boards.contains(&"leonardo"));
|
||||
assert!(boards.contains(&"micro"));
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ pub mod doctor;
|
||||
pub mod setup;
|
||||
pub mod devices;
|
||||
pub mod refresh;
|
||||
pub mod board;
|
||||
pub mod board;
|
||||
pub mod pin;
|
||||
1034
src/commands/pin.rs
Normal file
1034
src/commands/pin.rs
Normal file
File diff suppressed because it is too large
Load Diff
156
src/main.rs
156
src/main.rs
@@ -119,6 +119,60 @@ enum Commands {
|
||||
#[arg(long, short = 'd', value_name = "DIR")]
|
||||
dir: Option<String>,
|
||||
},
|
||||
|
||||
/// View pin maps, assign pins, and audit wiring
|
||||
Pin {
|
||||
/// Capability filter (pwm, analog, spi, i2c, uart, interrupt)
|
||||
/// or pin/bus name for --assign / --remove
|
||||
name: Option<String>,
|
||||
|
||||
/// Pin number or alias for --assign (e.g. 13, A0, SDA)
|
||||
pin: Option<String>,
|
||||
|
||||
/// Assign a pin or bus group
|
||||
#[arg(long, conflicts_with_all = ["remove", "audit", "generate", "capabilities", "init_from"])]
|
||||
assign: bool,
|
||||
|
||||
/// Remove a pin or bus assignment
|
||||
#[arg(long, conflicts_with_all = ["assign", "audit", "generate", "capabilities", "init_from"])]
|
||||
remove: bool,
|
||||
|
||||
/// Show wiring audit report
|
||||
#[arg(long, conflicts_with_all = ["assign", "remove", "generate", "capabilities", "init_from"])]
|
||||
audit: bool,
|
||||
|
||||
/// Show only the wiring checklist (with --audit)
|
||||
#[arg(long, requires = "audit")]
|
||||
brief: bool,
|
||||
|
||||
/// Generate lib/hal/pins.h
|
||||
#[arg(long, conflicts_with_all = ["assign", "remove", "audit", "capabilities", "init_from"])]
|
||||
generate: bool,
|
||||
|
||||
/// List capabilities supported by the board
|
||||
#[arg(long, conflicts_with_all = ["assign", "remove", "audit", "generate", "init_from"])]
|
||||
capabilities: bool,
|
||||
|
||||
/// Copy pin assignments from another board
|
||||
#[arg(long, value_name = "BOARD", conflicts_with_all = ["assign", "remove", "audit", "generate", "capabilities"])]
|
||||
init_from: Option<String>,
|
||||
|
||||
/// Pin mode (input, output, input_pullup, pwm, analog)
|
||||
#[arg(long, value_name = "MODE")]
|
||||
mode: Option<String>,
|
||||
|
||||
/// SPI chip-select pin (with --assign spi)
|
||||
#[arg(long, value_name = "PIN")]
|
||||
cs: Option<String>,
|
||||
|
||||
/// Target board (defaults to project default)
|
||||
#[arg(long, short = 'b', value_name = "BOARD")]
|
||||
board: Option<String>,
|
||||
|
||||
/// Path to project directory (defaults to current directory)
|
||||
#[arg(long, short = 'd', value_name = "DIR")]
|
||||
dir: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -225,6 +279,108 @@ fn main() -> Result<()> {
|
||||
commands::board::list_boards(dir.as_deref())
|
||||
}
|
||||
}
|
||||
Commands::Pin {
|
||||
name, pin, assign, remove, audit, brief,
|
||||
generate, capabilities, init_from, mode, cs, board, dir,
|
||||
} => {
|
||||
if capabilities {
|
||||
commands::pin::show_capabilities(
|
||||
board.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else if audit {
|
||||
commands::pin::audit_pins(
|
||||
board.as_deref(),
|
||||
brief,
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else if generate {
|
||||
commands::pin::generate_pins_header(
|
||||
board.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else if remove {
|
||||
let pin_name = name.as_deref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Name required.\n\
|
||||
Usage: anvil pin --remove red_led\n\
|
||||
Audit current: anvil pin --audit"
|
||||
)
|
||||
})?;
|
||||
commands::pin::remove_assignment(
|
||||
pin_name,
|
||||
board.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else if let Some(ref source) = init_from {
|
||||
let target = board.as_deref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Target board required.\n\
|
||||
Usage: anvil pin --init-from uno --board mega"
|
||||
)
|
||||
})?;
|
||||
commands::pin::init_from(
|
||||
source,
|
||||
target,
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else if assign {
|
||||
let assign_name = name.as_deref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Name required.\n\
|
||||
Usage: anvil pin --assign red_led 13\n\
|
||||
Usage: anvil pin --assign spi --cs 10\n\
|
||||
See pins: anvil pin"
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if this is a bus group assignment
|
||||
let is_bus = {
|
||||
let board_name = board.as_deref().unwrap_or("uno");
|
||||
anvil::board::pinmap::find_pinmap_fuzzy(board_name)
|
||||
.map(|pm| pm.groups.iter().any(|g| {
|
||||
g.name == assign_name.to_lowercase()
|
||||
}))
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
if is_bus {
|
||||
let mut user_pins: Vec<(&str, &str)> = Vec::new();
|
||||
if let Some(ref cs_pin) = cs {
|
||||
user_pins.push(("cs", cs_pin.as_str()));
|
||||
}
|
||||
commands::pin::assign_bus(
|
||||
assign_name,
|
||||
&user_pins,
|
||||
board.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
} else {
|
||||
let pin_str = pin.as_deref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Pin number required.\n\
|
||||
Usage: anvil pin --assign {} 13\n\
|
||||
See pins: anvil pin",
|
||||
assign_name
|
||||
)
|
||||
})?;
|
||||
commands::pin::assign_pin(
|
||||
assign_name,
|
||||
pin_str,
|
||||
mode.as_deref(),
|
||||
board.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Default: show pin map, optionally filtered
|
||||
commands::pin::show_pin_map(
|
||||
board.as_deref(),
|
||||
name.as_deref(),
|
||||
dir.as_deref(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct ProjectConfig {
|
||||
pub monitor: MonitorConfig,
|
||||
#[serde(default)]
|
||||
pub boards: HashMap<String, BoardProfile>,
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub pins: HashMap<String, BoardPinConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -51,6 +53,32 @@ pub struct BoardProfile {
|
||||
pub baud: Option<u32>,
|
||||
}
|
||||
|
||||
/// Pin configuration for a specific board.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct BoardPinConfig {
|
||||
/// Individual pin assignments: name -> { pin, mode }
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub assignments: HashMap<String, PinAssignment>,
|
||||
/// Bus group reservations: "spi" -> { cs = 10 }, "i2c" -> {}
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub buses: HashMap<String, BusConfig>,
|
||||
}
|
||||
|
||||
/// A single pin assignment with a friendly name.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PinAssignment {
|
||||
pub pin: u8,
|
||||
pub mode: String,
|
||||
}
|
||||
|
||||
/// Configuration for a claimed bus group.
|
||||
/// User-selectable pins (like CS for SPI) are stored as flattened keys.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct BusConfig {
|
||||
#[serde(flatten)]
|
||||
pub user_pins: HashMap<String, u8>,
|
||||
}
|
||||
|
||||
impl ProjectConfig {
|
||||
/// Create a new project config with sensible defaults.
|
||||
pub fn new(name: &str) -> Self {
|
||||
@@ -76,6 +104,7 @@ impl ProjectConfig {
|
||||
port: None,
|
||||
},
|
||||
boards,
|
||||
pins: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user