1use std::fmt::Write;
21use std::str::FromStr;
22
23use nucleus_db::{Database, Pin};
24
25use crate::config::{Config, Peripheral};
26use crate::model;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Generated {
31 pub config_h: String,
32 pub init_c: String,
33}
34
35struct Lowered {
37 instance: String,
39 handle: String,
41 handle_type: &'static str,
43 config_type: String,
45 kind: Kind,
46 pins: Vec<(Pin, u8, &'static str)>,
48}
49
50#[derive(Clone, Copy, PartialEq, Eq)]
51enum Kind {
52 Usart,
53 Spi,
54 I2c,
55 Tim,
56}
57
58pub fn generate(config: &Config, db: &Database) -> Generated {
60 let lowered: Vec<Lowered> = config
61 .peripherals
62 .iter()
63 .filter_map(|(instance, table)| lower(instance, table, db))
64 .collect();
65
66 Generated {
67 config_h: config_header(&lowered),
68 init_c: init_source(config, &lowered),
69 }
70}
71
72fn kind_of(instance: &str) -> Option<Kind> {
73 let prefix = instance.trim_end_matches(|c: char| c.is_ascii_digit());
74 match prefix {
75 "usart" | "uart" => Some(Kind::Usart),
76 "spi" => Some(Kind::Spi),
77 "i2c" | "fmpi2c" => Some(Kind::I2c),
78 "tim" => Some(Kind::Tim),
79 _ => None,
80 }
81}
82
83fn lower(instance: &str, table: &Peripheral, db: &Database) -> Option<Lowered> {
84 let kind = kind_of(instance)?;
85 let roles = model::roles_for(instance)?;
86 let name = model::peripheral_name(instance);
87 let digits: String = {
90 let rev: String = instance
91 .chars()
92 .rev()
93 .take_while(char::is_ascii_digit)
94 .collect();
95 rev.chars().rev().collect()
96 };
97
98 let (handle_prefix, handle_type) = match kind {
99 Kind::Usart => ("huart", "UART_HandleTypeDef"),
100 Kind::Spi => ("hspi", "SPI_HandleTypeDef"),
101 Kind::I2c => ("hi2c", "I2C_HandleTypeDef"),
102 Kind::Tim => ("htim", "TIM_HandleTypeDef"),
103 };
104
105 let mut pins = Vec::new();
106 for role in roles {
107 if let Some(value) = table.pin_str(role.key) {
108 if let Ok(pin) = Pin::from_str(value) {
109 if let Some(af) = db.find_af(pin, &name, role.signal) {
110 pins.push((pin, af, role.signal));
111 }
112 }
113 }
114 }
115
116 Some(Lowered {
117 config_type: format!("Nucleus_{name}_Config"),
118 handle: format!("{handle_prefix}{digits}"),
119 handle_type,
120 instance: name,
121 kind,
122 pins,
123 })
124}
125
126fn config_header(lowered: &[Lowered]) -> String {
127 let mut s = String::new();
128 s.push_str(GENERATED_BANNER);
129 s.push_str(
130 "#ifndef NUCLEUS_CONFIG_H\n\
131 #define NUCLEUS_CONFIG_H\n\n\
132 #include \"stm32f4xx_hal.h\"\n\n\
133 #ifdef __cplusplus\n\
134 extern \"C\" {\n\
135 #endif\n\n",
136 );
137
138 for p in lowered {
139 let _ = writeln!(s, "/* {} — resolved configuration */", p.instance);
140 let _ = writeln!(s, "typedef struct {{");
141 for field in p.kind.config_fields() {
142 let _ = writeln!(s, " uint32_t {field};");
143 }
144 let _ = writeln!(s, "}} {};", p.config_type);
145 let _ = writeln!(s, "extern {} {};\n", p.handle_type, p.handle);
146 }
147
148 s.push_str(
149 "/* Initializes every peripheral declared in stm32.toml. Call once after\n\
150 \x20 HAL_Init() and the system clock configuration. */\n\
151 void Nucleus_Init(void);\n\n\
152 #ifdef __cplusplus\n\
153 }\n\
154 #endif\n\n\
155 #endif /* NUCLEUS_CONFIG_H */\n",
156 );
157 s
158}
159
160fn init_source(config: &Config, lowered: &[Lowered]) -> String {
161 let mut s = String::new();
162 s.push_str(GENERATED_BANNER);
163 s.push_str("#include \"nucleus_config.h\"\n\n");
164
165 for p in lowered {
167 let _ = writeln!(s, "{} {};", p.handle_type, p.handle);
168 }
169 s.push('\n');
170
171 for p in lowered {
173 emit_config_instance(&mut s, config, p);
174 }
175
176 s.push_str("void Nucleus_Init(void)\n{\n");
177 s.push_str(" GPIO_InitTypeDef GPIO_InitStruct = {0};\n\n");
178
179 emit_gpio_clock_enables(&mut s, lowered);
180
181 for p in lowered {
182 let _ = writeln!(s, " /* ---- {} ---- */", p.instance);
183 emit_gpio_config(&mut s, p);
184 emit_peripheral_init(&mut s, p);
185 s.push('\n');
186 }
187
188 s.push_str("}\n");
189 s
190}
191
192fn emit_config_instance(s: &mut String, config: &Config, p: &Lowered) {
193 let var = format!("{}_config", p.instance.to_ascii_lowercase());
194 let table = &config.peripherals[&p.instance.to_ascii_lowercase()];
195 let _ = writeln!(s, "static const {} {} = {{", p.config_type, var);
196 match p.kind {
197 Kind::Usart => {
198 let baud = table
199 .0
200 .get("baud")
201 .and_then(toml::Value::as_integer)
202 .unwrap_or(115_200);
203 let _ = writeln!(s, " .BaudRate = {baud}u,");
204 }
205 Kind::Spi => {
206 let mode = table
207 .0
208 .get("mode")
209 .and_then(toml::Value::as_integer)
210 .unwrap_or(0);
211 let (cpol, cpha) = spi_mode(mode);
212 let _ = writeln!(s, " .CLKPolarity = {cpol},");
213 let _ = writeln!(s, " .CLKPhase = {cpha},");
214 }
215 Kind::I2c => {
216 let speed = table
217 .0
218 .get("speed")
219 .and_then(toml::Value::as_str)
220 .unwrap_or("standard");
221 let hz = if speed.eq_ignore_ascii_case("fast") {
222 400_000
223 } else {
224 100_000
225 };
226 let _ = writeln!(s, " .ClockSpeed = {hz}u,");
227 }
228 Kind::Tim => {
229 let (psc, arr) = tim_timing(config, table);
230 let _ = writeln!(s, " .Prescaler = {psc}u,");
231 let _ = writeln!(s, " .Period = {arr}u,");
232 }
233 }
234 let _ = writeln!(s, "}};\n");
235}
236
237fn emit_gpio_clock_enables(s: &mut String, lowered: &[Lowered]) {
238 let mut ports: Vec<char> = lowered
239 .iter()
240 .flat_map(|p| p.pins.iter().map(|(pin, _, _)| pin.port.letter()))
241 .collect();
242 ports.sort_unstable();
243 ports.dedup();
244 if ports.is_empty() {
245 return;
246 }
247 s.push_str(" /* GPIO port clocks */\n");
248 for port in ports {
249 let _ = writeln!(s, " __HAL_RCC_GPIO{port}_CLK_ENABLE();");
250 }
251 s.push('\n');
252}
253
254fn emit_gpio_config(s: &mut String, p: &Lowered) {
255 for (pin, af, _signal) in &p.pins {
256 let port = pin.port.letter();
257 let pull = if p.kind == Kind::I2c {
258 "GPIO_PULLUP"
259 } else {
260 "GPIO_NOPULL"
261 };
262 let mode = if p.kind == Kind::I2c {
263 "GPIO_MODE_AF_OD"
264 } else {
265 "GPIO_MODE_AF_PP"
266 };
267 let _ = writeln!(s, " GPIO_InitStruct.Pin = GPIO_PIN_{};", pin.number);
268 let _ = writeln!(s, " GPIO_InitStruct.Mode = {mode};");
269 let _ = writeln!(s, " GPIO_InitStruct.Pull = {pull};");
270 let _ = writeln!(s, " GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;");
271 let _ = writeln!(
272 s,
273 " GPIO_InitStruct.Alternate = GPIO_AF{af}_{};",
274 p.instance
275 );
276 let _ = writeln!(s, " HAL_GPIO_Init(GPIO{port}, &GPIO_InitStruct);");
277 }
278}
279
280fn emit_peripheral_init(s: &mut String, p: &Lowered) {
281 let h = &p.handle;
282 let cfg = format!("{}_config", p.instance.to_ascii_lowercase());
283 let _ = writeln!(s, " __HAL_RCC_{}_CLK_ENABLE();", p.instance);
284 let _ = writeln!(s, " {h}.Instance = {};", p.instance);
285 match p.kind {
286 Kind::Usart => {
287 let _ = writeln!(s, " {h}.Init.BaudRate = {cfg}.BaudRate;");
288 for (field, val) in [
289 ("WordLength", "UART_WORDLENGTH_8B"),
290 ("StopBits", "UART_STOPBITS_1"),
291 ("Parity", "UART_PARITY_NONE"),
292 ("Mode", "UART_MODE_TX_RX"),
293 ("HwFlowCtl", "UART_HWCONTROL_NONE"),
294 ("OverSampling", "UART_OVERSAMPLING_16"),
295 ] {
296 let _ = writeln!(s, " {h}.Init.{field} = {val};");
297 }
298 let _ = writeln!(s, " HAL_UART_Init(&{h});");
299 }
300 Kind::Spi => {
301 let _ = writeln!(s, " {h}.Init.CLKPolarity = {cfg}.CLKPolarity;");
302 let _ = writeln!(s, " {h}.Init.CLKPhase = {cfg}.CLKPhase;");
303 for (field, val) in [
304 ("Mode", "SPI_MODE_MASTER"),
305 ("Direction", "SPI_DIRECTION_2LINES"),
306 ("DataSize", "SPI_DATASIZE_8BIT"),
307 ("NSS", "SPI_NSS_SOFT"),
308 ("BaudRatePrescaler", "SPI_BAUDRATEPRESCALER_16"),
309 ("FirstBit", "SPI_FIRSTBIT_MSB"),
310 ("TIMode", "SPI_TIMODE_DISABLE"),
311 ("CRCCalculation", "SPI_CRCCALCULATION_DISABLE"),
312 ] {
313 let _ = writeln!(s, " {h}.Init.{field} = {val};");
314 }
315 let _ = writeln!(s, " HAL_SPI_Init(&{h});");
316 }
317 Kind::I2c => {
318 let _ = writeln!(s, " {h}.Init.ClockSpeed = {cfg}.ClockSpeed;");
319 for (field, val) in [
320 ("DutyCycle", "I2C_DUTYCYCLE_2"),
321 ("OwnAddress1", "0"),
322 ("AddressingMode", "I2C_ADDRESSINGMODE_7BIT"),
323 ("DualAddressMode", "I2C_DUALADDRESS_DISABLE"),
324 ("OwnAddress2", "0"),
325 ("GeneralCallMode", "I2C_GENERALCALL_DISABLE"),
326 ("NoStretchMode", "I2C_NOSTRETCH_DISABLE"),
327 ] {
328 let _ = writeln!(s, " {h}.Init.{field} = {val};");
329 }
330 let _ = writeln!(s, " HAL_I2C_Init(&{h});");
331 }
332 Kind::Tim => {
333 let _ = writeln!(s, " {h}.Init.Prescaler = {cfg}.Prescaler;");
334 let _ = writeln!(s, " {h}.Init.Period = {cfg}.Period;");
335 for (field, val) in [
336 ("CounterMode", "TIM_COUNTERMODE_UP"),
337 ("ClockDivision", "TIM_CLOCKDIVISION_DIV1"),
338 ("AutoReloadPreload", "TIM_AUTORELOAD_PRELOAD_ENABLE"),
339 ] {
340 let _ = writeln!(s, " {h}.Init.{field} = {val};");
341 }
342 let _ = writeln!(s, " HAL_TIM_PWM_Init(&{h});");
343 }
344 }
345}
346
347impl Kind {
348 fn config_fields(self) -> &'static [&'static str] {
349 match self {
350 Kind::Usart => &["BaudRate"],
351 Kind::Spi => &["CLKPolarity", "CLKPhase"],
352 Kind::I2c => &["ClockSpeed"],
353 Kind::Tim => &["Prescaler", "Period"],
354 }
355 }
356}
357
358fn spi_mode(mode: i64) -> (&'static str, &'static str) {
360 match mode {
361 1 => ("SPI_POLARITY_LOW", "SPI_PHASE_2EDGE"),
362 2 => ("SPI_POLARITY_HIGH", "SPI_PHASE_1EDGE"),
363 3 => ("SPI_POLARITY_HIGH", "SPI_PHASE_2EDGE"),
364 _ => ("SPI_POLARITY_LOW", "SPI_PHASE_1EDGE"),
365 }
366}
367
368fn tim_timing(config: &Config, table: &Peripheral) -> (u32, u32) {
372 let bits = table
373 .0
374 .get("duty_resolution_bits")
375 .and_then(toml::Value::as_integer)
376 .unwrap_or(16)
377 .clamp(1, 31) as u32;
378 let arr: u32 = (1u32 << bits) - 1;
379
380 let freq = table
381 .0
382 .get("frequency_hz")
383 .and_then(toml::Value::as_integer)
384 .unwrap_or(1000)
385 .max(1) as u64;
386 let timer_clk = config.device.clock_hz.unwrap_or(180_000_000).max(1);
387
388 let divisor = freq * (arr as u64 + 1);
390 let psc = (timer_clk / divisor).saturating_sub(1);
391 (psc.min(u32::MAX as u64) as u32, arr)
392}
393
394const GENERATED_BANNER: &str = "\
395/* Generated by Nucleus — do not edit by hand.\n\
396\x20* Regenerate with `nucleus build`. Source of truth: stm32.toml. */\n\n";
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401 use crate::config;
402
403 fn gen(text: &str) -> Generated {
404 let cfg = config::parse(text).unwrap();
405 generate(&cfg, &Database::f446re())
406 }
407
408 const EXAMPLE: &str = r#"
409[device]
410family = "STM32F446RE"
411clock_hz = 180_000_000
412
413[peripherals.usart2]
414tx = "PA2"
415rx = "PA3"
416baud = 115200
417
418[peripherals.spi1]
419mosi = "PA7"
420miso = "PA6"
421sck = "PA5"
422nss = "PA4"
423mode = 0
424
425[peripherals.i2c1]
426sda = "PB9"
427scl = "PB8"
428speed = "fast"
429
430[peripherals.tim2]
431channel1 = "PA0"
432frequency_hz = 1000
433duty_resolution_bits = 16
434"#;
435
436 #[test]
437 fn header_declares_handles_and_prototype() {
438 let g = gen(EXAMPLE);
439 assert!(g.config_h.contains("extern UART_HandleTypeDef huart2;"));
440 assert!(g.config_h.contains("typedef struct {"));
441 assert!(g.config_h.contains("void Nucleus_Init(void);"));
442 assert!(g.config_h.contains("#ifndef NUCLEUS_CONFIG_H"));
443 }
444
445 #[test]
446 fn init_calls_stock_hal_init_functions() {
447 let g = gen(EXAMPLE);
448 for call in [
449 "HAL_UART_Init(&huart2);",
450 "HAL_SPI_Init(&hspi1);",
451 "HAL_I2C_Init(&hi2c1);",
452 "HAL_TIM_PWM_Init(&htim2);",
453 ] {
454 assert!(g.init_c.contains(call), "missing {call}\n{}", g.init_c);
455 }
456 assert_eq!(g.init_c.matches("void Nucleus_Init(void)").count(), 1);
458 }
459
460 #[test]
461 fn gpio_uses_af_numbers_from_database() {
462 let g = gen(EXAMPLE);
463 assert!(g.init_c.contains("GPIO_InitStruct.Pin = GPIO_PIN_2;"));
465 assert!(g.init_c.contains("GPIO_AF7_USART2;"));
466 assert!(g.init_c.contains("GPIO_AF5_SPI1;"));
467 assert!(g.init_c.contains("GPIO_AF4_I2C1;"));
468 }
469
470 #[test]
471 fn enables_each_gpio_port_clock_once() {
472 let g = gen(EXAMPLE);
473 assert_eq!(g.init_c.matches("__HAL_RCC_GPIOA_CLK_ENABLE();").count(), 1);
474 assert_eq!(g.init_c.matches("__HAL_RCC_GPIOB_CLK_ENABLE();").count(), 1);
475 }
476
477 #[test]
478 fn i2c_pins_are_open_drain_with_pullups() {
479 let g = gen(EXAMPLE);
480 assert!(g.init_c.contains("GPIO_MODE_AF_OD"));
481 assert!(g.init_c.contains("GPIO_PULLUP"));
482 }
483
484 #[test]
485 fn resolved_params_land_in_config_structs() {
486 let g = gen(EXAMPLE);
487 assert!(g.init_c.contains(".BaudRate = 115200u,"));
488 assert!(g.init_c.contains(".ClockSpeed = 400000u,")); assert!(g.init_c.contains(".CLKPolarity = SPI_POLARITY_LOW,")); }
491
492 #[test]
493 fn output_is_deterministic() {
494 assert_eq!(gen(EXAMPLE), gen(EXAMPLE));
495 }
496
497 #[test]
498 fn empty_config_still_emits_valid_init() {
499 let g = gen("[device]\nfamily = \"STM32F446RE\"\n");
500 assert!(g.init_c.contains("void Nucleus_Init(void)"));
501 assert!(g.config_h.contains("void Nucleus_Init(void);"));
502 }
503}