nucleus_compiler/
config.rs1use std::collections::BTreeMap;
15
16use serde::Deserialize;
17
18#[derive(Debug, Clone, Default, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct Config {
22 #[serde(default)]
23 pub device: Device,
24 #[serde(default)]
25 pub build: Build,
26 #[serde(default)]
30 pub peripherals: BTreeMap<String, Peripheral>,
31 #[serde(default)]
34 pub clocks: Clocks,
35 #[serde(default)]
36 pub trace: Trace,
37}
38
39#[derive(Debug, Clone, Default, Deserialize)]
41#[serde(deny_unknown_fields)]
42pub struct Device {
43 #[serde(default)]
44 pub family: String,
45 #[serde(default)]
46 pub board: Option<String>,
47 #[serde(default)]
48 pub clock_hz: Option<u64>,
49}
50
51#[derive(Debug, Clone, Default, Deserialize)]
53#[serde(deny_unknown_fields)]
54pub struct Build {
55 #[serde(default)]
56 pub toolchain: Option<String>,
57 #[serde(default)]
58 pub optimization: Option<String>,
59}
60
61#[derive(Debug, Clone, Default, Deserialize)]
64#[serde(transparent)]
65pub struct Peripheral(pub BTreeMap<String, toml::Value>);
66
67impl Peripheral {
68 pub fn pin_str(&self, key: &str) -> Option<&str> {
70 self.0.get(key).and_then(toml::Value::as_str)
71 }
72}
73
74#[derive(Debug, Clone, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct Clocks {
79 #[serde(default = "enabled")]
80 pub ahb1: bool,
81 #[serde(default = "enabled")]
82 pub apb1: bool,
83 #[serde(default = "enabled")]
84 pub apb2: bool,
85}
86
87fn enabled() -> bool {
88 true
89}
90
91impl Default for Clocks {
92 fn default() -> Clocks {
93 Clocks {
94 ahb1: true,
95 apb1: true,
96 apb2: true,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Default, Deserialize)]
103#[serde(deny_unknown_fields)]
104pub struct Trace {
105 #[serde(default)]
106 pub enabled: bool,
107 #[serde(default)]
108 pub swo_freq: Option<u64>,
109 #[serde(default)]
110 pub variables: Vec<TraceVariable>,
111}
112
113#[derive(Debug, Clone, Default, Deserialize)]
115#[serde(deny_unknown_fields)]
116pub struct TraceVariable {
117 pub name: String,
118 pub port: u8,
119 #[serde(rename = "type")]
120 pub ty: String,
121}
122
123#[derive(Debug)]
125pub struct ParseError(toml::de::Error);
126
127impl ParseError {
128 pub fn span(&self) -> Option<std::ops::Range<usize>> {
131 self.0.span()
132 }
133
134 pub fn message(&self) -> &str {
136 self.0.message()
137 }
138}
139
140impl std::fmt::Display for ParseError {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 write!(f, "invalid stm32.toml: {}", self.0.message())
143 }
144}
145
146impl std::error::Error for ParseError {}
147
148pub fn parse(text: &str) -> Result<Config, ParseError> {
150 toml::from_str(text).map_err(ParseError)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn parses_readme_example() {
159 let cfg = parse(
160 r#"
161[device]
162family = "STM32F446RE"
163board = "NUCLEO-F446RE"
164clock_hz = 180_000_000
165
166[peripherals.usart2]
167tx = "PA2"
168rx = "PA3"
169baud = 115200
170
171[peripherals.spi1]
172mosi = "PA7"
173miso = "PA6"
174sck = "PA5"
175nss = "PA4"
176mode = 0
177"#,
178 )
179 .unwrap();
180
181 assert_eq!(cfg.device.family, "STM32F446RE");
182 assert_eq!(cfg.peripherals.len(), 2);
183 assert_eq!(cfg.peripherals["usart2"].pin_str("tx"), Some("PA2"));
184 assert_eq!(cfg.peripherals["spi1"].pin_str("mode"), None);
186 assert!(cfg.clocks.apb1 && cfg.clocks.apb2 && cfg.clocks.ahb1);
188 }
189
190 #[test]
191 fn rejects_unknown_top_level_section() {
192 assert!(parse("[nonsense]\nfoo = 1\n").is_err());
193 }
194
195 #[test]
196 fn clocks_section_can_disable_a_bus() {
197 let cfg = parse("[clocks]\napb1 = false\n").unwrap();
198 assert!(!cfg.clocks.apb1);
199 assert!(cfg.clocks.apb2); }
201}