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;
|
use std::process::Command;
|
||||||
|
|
||||||
pub mod presets;
|
pub mod presets;
|
||||||
|
pub mod pinmap;
|
||||||
|
|
||||||
/// Information about a detected serial port.
|
/// Information about a detected serial port.
|
||||||
#[derive(Debug, Clone)]
|
#[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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,4 @@ pub mod setup;
|
|||||||
pub mod devices;
|
pub mod devices;
|
||||||
pub mod refresh;
|
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")]
|
#[arg(long, short = 'd', value_name = "DIR")]
|
||||||
dir: Option<String>,
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
@@ -225,6 +279,108 @@ fn main() -> Result<()> {
|
|||||||
commands::board::list_boards(dir.as_deref())
|
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,
|
pub monitor: MonitorConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub boards: HashMap<String, BoardProfile>,
|
pub boards: HashMap<String, BoardProfile>,
|
||||||
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
pub pins: HashMap<String, BoardPinConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@@ -51,6 +53,32 @@ pub struct BoardProfile {
|
|||||||
pub baud: Option<u32>,
|
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 {
|
impl ProjectConfig {
|
||||||
/// Create a new project config with sensible defaults.
|
/// Create a new project config with sensible defaults.
|
||||||
pub fn new(name: &str) -> Self {
|
pub fn new(name: &str) -> Self {
|
||||||
@@ -76,6 +104,7 @@ impl ProjectConfig {
|
|||||||
port: None,
|
port: None,
|
||||||
},
|
},
|
||||||
boards,
|
boards,
|
||||||
|
pins: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1971,3 +1971,274 @@ fn test_monitor_sh_timestamps_work_in_watch_mode() {
|
|||||||
filter_count
|
filter_count
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Pin map data
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_exists_for_all_presets() {
|
||||||
|
use anvil::board::presets;
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
// Every board preset (except nano-old) should have a pin map
|
||||||
|
for preset in presets::PRESETS {
|
||||||
|
let found = pinmap::find_pinmap_fuzzy(preset.name);
|
||||||
|
assert!(
|
||||||
|
found.is_some(),
|
||||||
|
"Board preset '{}' has no pin map. Add one to pinmap.rs.",
|
||||||
|
preset.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_uno_basics() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
assert_eq!(m.total_digital, 14);
|
||||||
|
assert_eq!(m.total_analog, 6);
|
||||||
|
// Uno has 20 pins total (D0-D13 + A0-A5 mapped as D14-D19)
|
||||||
|
assert_eq!(m.pins.len(), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_mega_basics() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("mega").unwrap();
|
||||||
|
assert_eq!(m.total_digital, 54);
|
||||||
|
assert_eq!(m.total_analog, 16);
|
||||||
|
// Mega has 70 pins: D0-D53 + A0-A15
|
||||||
|
assert_eq!(m.pins.len(), 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_resolve_alias_a0() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
assert_eq!(pinmap::resolve_alias(uno, "A0"), Some(14));
|
||||||
|
assert_eq!(pinmap::resolve_alias(uno, "a0"), Some(14));
|
||||||
|
|
||||||
|
let mega = pinmap::find_pinmap("mega").unwrap();
|
||||||
|
assert_eq!(pinmap::resolve_alias(mega, "A0"), Some(54));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_spi_pins_differ_by_board() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
let mega = pinmap::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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Uno SCK=13, Mega SCK=52
|
||||||
|
assert_ne!(uno_sck, mega_sck, "SPI SCK should differ between uno and mega");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_capabilities_filter() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
let pwm = pinmap::pins_with_capability(m, "pwm");
|
||||||
|
// Uno has PWM on 3, 5, 6, 9, 10, 11
|
||||||
|
assert_eq!(pwm.len(), 6);
|
||||||
|
|
||||||
|
let analog = pinmap::pins_with_capability(m, "analog");
|
||||||
|
assert_eq!(analog.len(), 6); // A0-A5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_board_capabilities_list() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
let caps = pinmap::board_capabilities(m);
|
||||||
|
assert!(caps.contains(&"digital"));
|
||||||
|
assert!(caps.contains(&"analog"));
|
||||||
|
assert!(caps.contains(&"pwm"));
|
||||||
|
assert!(caps.contains(&"spi"));
|
||||||
|
assert!(caps.contains(&"i2c"));
|
||||||
|
assert!(caps.contains(&"uart"));
|
||||||
|
assert!(caps.contains(&"interrupt"));
|
||||||
|
assert!(caps.contains(&"led"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_mega_has_four_uarts() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("mega").unwrap();
|
||||||
|
let uart_count = m.groups.iter()
|
||||||
|
.filter(|g| g.name.starts_with("uart"))
|
||||||
|
.count();
|
||||||
|
assert_eq!(uart_count, 4, "Mega should have uart, uart1, uart2, uart3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_leonardo_i2c_different_from_uno() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
||||||
|
let leo = pinmap::find_pinmap("leonardo").unwrap();
|
||||||
|
|
||||||
|
let uno_i2c = uno.groups.iter().find(|g| g.name == "i2c").unwrap();
|
||||||
|
let leo_i2c = leo.groups.iter().find(|g| g.name == "i2c").unwrap();
|
||||||
|
|
||||||
|
let uno_sda = uno_i2c.fixed_pins.iter().find(|p| p.0 == "sda").unwrap().1;
|
||||||
|
let leo_sda = leo_i2c.fixed_pins.iter().find(|p| p.0 == "sda").unwrap().1;
|
||||||
|
|
||||||
|
// Uno SDA=18 (A4), Leonardo SDA=2
|
||||||
|
assert_ne!(uno_sda, leo_sda, "I2C SDA should differ between uno and leonardo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_spi_has_user_selectable_cs() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
// All boards with SPI should require user to select CS
|
||||||
|
for board in pinmap::available_boards() {
|
||||||
|
let m = pinmap::find_pinmap(board).unwrap();
|
||||||
|
if let Some(spi) = m.groups.iter().find(|g| g.name == "spi") {
|
||||||
|
assert!(
|
||||||
|
spi.user_selectable.contains(&"cs"),
|
||||||
|
"Board '{}' SPI should have user-selectable CS pin",
|
||||||
|
board
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pinmap_nano_has_analog_only_pins() {
|
||||||
|
use anvil::board::pinmap;
|
||||||
|
|
||||||
|
let m = pinmap::find_pinmap("nano").unwrap();
|
||||||
|
let a6 = pinmap::get_pin(m, 20).unwrap(); // A6
|
||||||
|
assert!(a6.capabilities.contains(&"analog"));
|
||||||
|
assert!(!a6.capabilities.contains(&"digital"),
|
||||||
|
"Nano A6 should be analog-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Pin config in .anvil.toml
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_with_pins_roundtrips() {
|
||||||
|
use anvil::project::config::{BoardPinConfig, PinAssignment, BusConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let mut config = ProjectConfig::new("pin_test");
|
||||||
|
|
||||||
|
// Add pin assignments
|
||||||
|
let mut assignments = HashMap::new();
|
||||||
|
assignments.insert("red_led".to_string(), PinAssignment {
|
||||||
|
pin: 13,
|
||||||
|
mode: "output".to_string(),
|
||||||
|
});
|
||||||
|
assignments.insert("limit_switch".to_string(), PinAssignment {
|
||||||
|
pin: 7,
|
||||||
|
mode: "input".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut buses = HashMap::new();
|
||||||
|
let mut spi_pins = HashMap::new();
|
||||||
|
spi_pins.insert("cs".to_string(), 10u8);
|
||||||
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
||||||
|
buses.insert("i2c".to_string(), BusConfig { user_pins: HashMap::new() });
|
||||||
|
|
||||||
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
||||||
|
assignments,
|
||||||
|
buses,
|
||||||
|
});
|
||||||
|
|
||||||
|
config.save(tmp.path()).unwrap();
|
||||||
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
||||||
|
|
||||||
|
let pc = loaded.pins.get("uno").unwrap();
|
||||||
|
assert_eq!(pc.assignments.len(), 2);
|
||||||
|
assert_eq!(pc.assignments["red_led"].pin, 13);
|
||||||
|
assert_eq!(pc.assignments["red_led"].mode, "output");
|
||||||
|
assert_eq!(pc.assignments["limit_switch"].pin, 7);
|
||||||
|
assert_eq!(pc.buses.len(), 2);
|
||||||
|
assert_eq!(*pc.buses["spi"].user_pins.get("cs").unwrap(), 10u8);
|
||||||
|
assert!(pc.buses["i2c"].user_pins.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_without_pins_loads_empty() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let config = ProjectConfig::new("no_pins");
|
||||||
|
config.save(tmp.path()).unwrap();
|
||||||
|
|
||||||
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
||||||
|
assert!(loaded.pins.is_empty(), "Config without pins section should load with empty pins");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_pins_skip_serializing_when_empty() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let config = ProjectConfig::new("empty_pins");
|
||||||
|
config.save(tmp.path()).unwrap();
|
||||||
|
|
||||||
|
let content = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
||||||
|
assert!(
|
||||||
|
!content.contains("[pins"),
|
||||||
|
"Empty pins should not appear in serialized config"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pins_per_board_independence() {
|
||||||
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let mut config = ProjectConfig::new("multi_board_pins");
|
||||||
|
config.boards.insert("mega".to_string(), BoardProfile {
|
||||||
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
||||||
|
baud: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Different pin numbers for same friendly name on different boards
|
||||||
|
let mut uno_assigns = HashMap::new();
|
||||||
|
uno_assigns.insert("status_led".to_string(), PinAssignment {
|
||||||
|
pin: 13, mode: "output".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut mega_assigns = HashMap::new();
|
||||||
|
mega_assigns.insert("status_led".to_string(), PinAssignment {
|
||||||
|
pin: 13, mode: "output".to_string(),
|
||||||
|
});
|
||||||
|
mega_assigns.insert("extra_led".to_string(), PinAssignment {
|
||||||
|
pin: 22, mode: "output".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
||||||
|
assignments: uno_assigns, buses: HashMap::new(),
|
||||||
|
});
|
||||||
|
config.pins.insert("mega".to_string(), BoardPinConfig {
|
||||||
|
assignments: mega_assigns, buses: HashMap::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
config.save(tmp.path()).unwrap();
|
||||||
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
||||||
|
|
||||||
|
let uno_pins = loaded.pins.get("uno").unwrap();
|
||||||
|
let mega_pins = loaded.pins.get("mega").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(uno_pins.assignments.len(), 1);
|
||||||
|
assert_eq!(mega_pins.assignments.len(), 2);
|
||||||
|
assert!(mega_pins.assignments.contains_key("extra_led"));
|
||||||
|
assert!(!uno_pins.assignments.contains_key("extra_led"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user