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    /// Every mapping for `pin` at alternate function `af`.
136    ///
137    /// Returns an iterator because one (pin, AF) can carry several peripheral
138    /// signals — e.g. on the F446 PA4/AF5 is both `SPI1_NSS` and `I2S1_WS`
139    /// (SPI and I2S share the same hardware block and AF number).
140    pub fn lookup(&self, pin: Pin, af: u8) -> impl Iterator<Item = &AfMapping> {
141        self.entries
142            .iter()
143            .filter(move |m| m.pin == pin && m.af == af)
144    }
145
146    /// Every alternate-function mapping available on `pin`.
147    pub fn alt_functions(&self, pin: Pin) -> impl Iterator<Item = &AfMapping> {
148        self.entries.iter().filter(move |m| m.pin == pin)
149    }
150
151    /// Every physical pin that has at least one alternate function, sorted and
152    /// deduplicated. Used by the LSP to offer pin-name completions.
153    pub fn pins(&self) -> Vec<Pin> {
154        let mut pins: Vec<Pin> = self.entries.iter().map(|m| m.pin).collect();
155        pins.sort_unstable();
156        pins.dedup();
157        pins
158    }
159
160    /// Reverse lookup: the AF number that connects `pin` to
161    /// `peripheral`'s `signal`, if any. Used by the constraint solver.
162    pub fn find_af(&self, pin: Pin, peripheral: &str, signal: &str) -> Option<u8> {
163        self.entries
164            .iter()
165            .find(|m| m.pin == pin && m.peripheral == peripheral && m.signal == signal)
166            .map(|m| m.af)
167    }
168}
169
170#[cfg(test)]
171mod tests;