use std::collections::BTreeMap;
use serde::Deserialize;
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub device: Device,
#[serde(default)]
pub build: Build,
#[serde(default)]
pub peripherals: BTreeMap<String, Peripheral>,
#[serde(default)]
pub clocks: Clocks,
#[serde(default)]
pub trace: Trace,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Device {
#[serde(default)]
pub family: String,
#[serde(default)]
pub board: Option<String>,
#[serde(default)]
pub clock_hz: Option<u64>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Build {
#[serde(default)]
pub toolchain: Option<String>,
#[serde(default)]
pub optimization: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(transparent)]
pub struct Peripheral(pub BTreeMap<String, toml::Value>);
impl Peripheral {
pub fn pin_str(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(toml::Value::as_str)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Clocks {
#[serde(default = "enabled")]
pub ahb1: bool,
#[serde(default = "enabled")]
pub apb1: bool,
#[serde(default = "enabled")]
pub apb2: bool,
}
fn enabled() -> bool {
true
}
impl Default for Clocks {
fn default() -> Clocks {
Clocks {
ahb1: true,
apb1: true,
apb2: true,
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Trace {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub swo_freq: Option<u64>,
#[serde(default)]
pub variables: Vec<TraceVariable>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TraceVariable {
pub name: String,
pub port: u8,
#[serde(rename = "type")]
pub ty: String,
}
#[derive(Debug)]
pub struct ParseError(toml::de::Error);
impl ParseError {
pub fn span(&self) -> Option<std::ops::Range<usize>> {
self.0.span()
}
pub fn message(&self) -> &str {
self.0.message()
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid stm32.toml: {}", self.0.message())
}
}
impl std::error::Error for ParseError {}
pub fn parse(text: &str) -> Result<Config, ParseError> {
toml::from_str(text).map_err(ParseError)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_readme_example() {
let cfg = parse(
r#"
[device]
family = "STM32F446RE"
board = "NUCLEO-F446RE"
clock_hz = 180_000_000
[peripherals.usart2]
tx = "PA2"
rx = "PA3"
baud = 115200
[peripherals.spi1]
mosi = "PA7"
miso = "PA6"
sck = "PA5"
nss = "PA4"
mode = 0
"#,
)
.unwrap();
assert_eq!(cfg.device.family, "STM32F446RE");
assert_eq!(cfg.peripherals.len(), 2);
assert_eq!(cfg.peripherals["usart2"].pin_str("tx"), Some("PA2"));
assert_eq!(cfg.peripherals["spi1"].pin_str("mode"), None);
assert!(cfg.clocks.apb1 && cfg.clocks.apb2 && cfg.clocks.ahb1);
}
#[test]
fn rejects_unknown_top_level_section() {
assert!(parse("[nonsense]\nfoo = 1\n").is_err());
}
#[test]
fn clocks_section_can_disable_a_bus() {
let cfg = parse("[clocks]\napb1 = false\n").unwrap();
assert!(!cfg.clocks.apb1);
assert!(cfg.clocks.apb2); }
}