Skip to main content

nucleus_db/
lib.rs

1//! STM32 constraint database for the Nucleus toolchain.
2//!
3//! Exposes a normalized pin ↔ alternate-function ↔ peripheral model and the
4//! lookup APIs the compiler and LSP build on. The data is currently a
5//! hand-verified seed for the **STM32F446RE**; the full CMSIS Device Family
6//! Pack parser that will populate it lives in [`pack`] and is not yet wired up.
7//!
8//! See the Phase 1 exit criteria in the project README.
9
10use std::fmt;
11use std::str::FromStr;
12
13mod data;
14pub mod pack;
15
16/// A GPIO port on the device.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub enum Port {
19    A,
20    B,
21    C,
22    D,
23    E,
24    F,
25    G,
26    H,
27}
28
29impl Port {
30    /// The port's letter, e.g. `Port::A` → `'A'`.
31    pub const fn letter(self) -> char {
32        match self {
33            Port::A => 'A',
34            Port::B => 'B',
35            Port::C => 'C',
36            Port::D => 'D',
37            Port::E => 'E',
38            Port::F => 'F',
39            Port::G => 'G',
40            Port::H => 'H',
41        }
42    }
43
44    const fn from_letter(c: char) -> Option<Port> {
45        match c {
46            'A' | 'a' => Some(Port::A),
47            'B' | 'b' => Some(Port::B),
48            'C' | 'c' => Some(Port::C),
49            'D' | 'd' => Some(Port::D),
50            'E' | 'e' => Some(Port::E),
51            'F' | 'f' => Some(Port::F),
52            'G' | 'g' => Some(Port::G),
53            'H' | 'h' => Some(Port::H),
54            _ => None,
55        }
56    }
57}
58
59/// A physical pin, e.g. `PA7` is `Port::A` pin `7`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
61pub struct Pin {
62    pub port: Port,
63    pub number: u8,
64}
65
66impl Pin {
67    pub const fn new(port: Port, number: u8) -> Pin {
68        Pin { port, number }
69    }
70}
71
72impl fmt::Display for Pin {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "P{}{}", self.port.letter(), self.number)
75    }
76}
77
78/// Error returned when a string cannot be parsed as a [`Pin`].
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct ParsePinError(String);
81
82impl fmt::Display for ParsePinError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "invalid pin name: {:?} (expected e.g. \"PA7\")", self.0)
85    }
86}
87
88impl std::error::Error for ParsePinError {}
89
90impl FromStr for Pin {
91    type Err = ParsePinError;
92
93    fn from_str(s: &str) -> Result<Pin, ParsePinError> {
94        let err = || ParsePinError(s.to_string());
95        let rest = s
96            .strip_prefix('P')
97            .or_else(|| s.strip_prefix('p'))
98            .ok_or_else(err)?;
99        let mut chars = rest.chars();
100        let port = chars.next().and_then(Port::from_letter).ok_or_else(err)?;
101        let number: u8 = chars.as_str().parse().map_err(|_| err())?;
102        if number > 15 {
103            return Err(err());
104        }
105        Ok(Pin { port, number })
106    }
107}
108
109/// One alternate-function mapping: a [`Pin`] driving a peripheral signal at a
110/// given AF number (AF0–AF15).
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct AfMapping {
113    pub pin: Pin,
114    pub af: u8,
115    /// Peripheral instance, e.g. `"SPI1"`.
116    pub peripheral: &'static str,
117    /// Signal on that peripheral, e.g. `"MOSI"`.
118    pub signal: &'static str,
119}
120
121/// A queryable constraint database for a single device.
122#[derive(Debug, Clone, Copy)]
123pub struct Database {
124    entries: &'static [AfMapping],
125}
126
127impl Database {
128    /// The hand-verified database for the NUCLEO-F446RE's STM32F446RE.
129    pub const fn f446re() -> Database {
130        Database {
131            entries: data::F446RE,
132        }
133    }
134
135    /// The database for the NUCLEO-F411RE's STM32F411RE, generated at build
136    /// time from the vendored ST pin data (see [`pack`]).
137    pub const fn f411re() -> Database {
138        Database {
139            entries: data::F411RE,
140        }
141    }
142
143    /// Whether this device exposes `peripheral` at all (any AF mapping names it).
144    /// Used by the solver to flag peripherals absent on the selected family.
145    pub fn has_peripheral(&self, peripheral: &str) -> bool {
146        self.entries.iter().any(|m| m.peripheral == peripheral)
147    }
148
149    /// Every mapping for `pin` at alternate function `af`.
150    ///
151    /// Returns an iterator because one (pin, AF) can carry several peripheral
152    /// signals — e.g. on the F446 PA4/AF5 is both `SPI1_NSS` and `I2S1_WS`
153    /// (SPI and I2S share the same hardware block and AF number).
154    pub fn lookup(&self, pin: Pin, af: u8) -> impl Iterator<Item = &AfMapping> {
155        self.entries
156            .iter()
157            .filter(move |m| m.pin == pin && m.af == af)
158    }
159
160    /// Every alternate-function mapping available on `pin`.
161    pub fn alt_functions(&self, pin: Pin) -> impl Iterator<Item = &AfMapping> {
162        self.entries.iter().filter(move |m| m.pin == pin)
163    }
164
165    /// Every physical pin that has at least one alternate function, sorted and
166    /// deduplicated. Used by the LSP to offer pin-name completions.
167    pub fn pins(&self) -> Vec<Pin> {
168        let mut pins: Vec<Pin> = self.entries.iter().map(|m| m.pin).collect();
169        pins.sort_unstable();
170        pins.dedup();
171        pins
172    }
173
174    /// Reverse lookup: the AF number that connects `pin` to
175    /// `peripheral`'s `signal`, if any. Used by the constraint solver.
176    pub fn find_af(&self, pin: Pin, peripheral: &str, signal: &str) -> Option<u8> {
177        self.entries
178            .iter()
179            .find(|m| m.pin == pin && m.peripheral == peripheral && m.signal == signal)
180            .map(|m| m.af)
181    }
182}
183
184#[cfg(test)]
185mod tests;