Skip to content

Commit c4e5a72

Browse files
committed
Add codegen for GPIO mappings
1 parent 4352ea8 commit c4e5a72

File tree

11 files changed

+431
-0
lines changed

11 files changed

+431
-0
lines changed

codegen/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target/
2+
**/*.rs.bk

codegen/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "codegen"
3+
version = "0.1.0"
4+
authors = ["Jan Teske <jteske@posteo.net>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
anyhow = "1"
9+
once_cell = "1"
10+
regex = "1"
11+
serde-xml-rs = "0.4"
12+
13+
[dependencies.clap]
14+
version = "2"
15+
default-features = false
16+
17+
[dependencies.serde]
18+
version = "1"
19+
features = ["derive"]

codegen/src/codegen/gpio.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use crate::cubemx::ip::gpio;
2+
use anyhow::{Context, Result};
3+
use once_cell::sync::Lazy;
4+
use regex::Regex;
5+
use std::collections::HashMap;
6+
7+
struct Port<'a> {
8+
id: char,
9+
pins: Vec<&'a gpio::Pin>,
10+
}
11+
12+
pub fn gen_mappings(gpio_ips: &[gpio::Ip]) -> Result<()> {
13+
for ip in gpio_ips.iter() {
14+
println!();
15+
gen_gpio_ip(ip)?;
16+
}
17+
Ok(())
18+
}
19+
20+
fn gen_gpio_ip(ip: &gpio::Ip) -> Result<()> {
21+
let feature = ip_version_to_feature(&ip.version)?;
22+
let ports = merge_pins_by_port(&ip.pins)?;
23+
24+
println!(r#"#[cfg(feature = "{}")]"#, feature);
25+
gen_gpio_macro_call(&ports)?;
26+
Ok(())
27+
}
28+
29+
fn ip_version_to_feature(ip_version: &str) -> Result<String> {
30+
static VERSION: Lazy<Regex> =
31+
Lazy::new(|| Regex::new(r"^STM32(?P<version>\w+)_gpio_v1_0$").unwrap());
32+
33+
let captures = VERSION
34+
.captures(&ip_version)
35+
.with_context(|| format!("invalid GPIO IP version: {}", ip_version))?;
36+
37+
let version = captures.name("version").unwrap().as_str();
38+
let feature = format!("gpio-{}", version.to_lowercase());
39+
Ok(feature)
40+
}
41+
42+
fn merge_pins_by_port(pins: &[gpio::Pin]) -> Result<Vec<Port>> {
43+
let mut pins_by_port = HashMap::new();
44+
for pin in pins.iter() {
45+
pins_by_port
46+
.entry(pin.port()?)
47+
.and_modify(|e: &mut Vec<_>| e.push(pin))
48+
.or_insert_with(|| vec![pin]);
49+
}
50+
51+
let mut ports = Vec::new();
52+
for (id, mut pins) in pins_by_port {
53+
pins.sort_by_key(|p| p.number().unwrap_or_default());
54+
pins.dedup_by_key(|p| p.number().unwrap_or_default());
55+
ports.push(Port { id, pins });
56+
}
57+
ports.sort_by_key(|p| p.id);
58+
59+
Ok(ports)
60+
}
61+
62+
fn gen_gpio_macro_call(ports: &[Port]) -> Result<()> {
63+
println!("gpio!([");
64+
for port in ports {
65+
gen_port(port)?;
66+
}
67+
println!("]);");
68+
Ok(())
69+
}
70+
71+
fn gen_port(port: &Port) -> Result<()> {
72+
let pac_module = get_port_pac_module(port);
73+
74+
println!(" {{");
75+
println!(
76+
" port: ({}/{}, pac: {}),",
77+
port.id,
78+
port.id.to_lowercase(),
79+
pac_module,
80+
);
81+
println!(" pins: [");
82+
83+
for pin in port.pins.iter() {
84+
gen_pin(pin)?;
85+
}
86+
87+
println!(" ],");
88+
println!(" }},");
89+
Ok(())
90+
}
91+
92+
fn get_port_pac_module(port: &Port) -> &'static str {
93+
// The registers in ports A and B have different reset values due to the
94+
// presence of debug pins, so they get dedicated PAC modules.
95+
match port.id {
96+
'A' => "gpioa",
97+
'B' => "gpiob",
98+
_ => "gpioc",
99+
}
100+
}
101+
102+
fn gen_pin(pin: &gpio::Pin) -> Result<()> {
103+
let nr = pin.number()?;
104+
let reset_mode = get_pin_reset_mode(pin)?;
105+
let afr = if nr < 8 { 'L' } else { 'H' };
106+
let af_numbers = get_pin_af_numbers(pin)?;
107+
108+
println!(
109+
" {} => {{ reset: {}, afr: {}/{}, af: {:?} }},",
110+
nr,
111+
reset_mode,
112+
afr,
113+
afr.to_lowercase(),
114+
af_numbers,
115+
);
116+
117+
Ok(())
118+
}
119+
120+
fn get_pin_reset_mode(pin: &gpio::Pin) -> Result<&'static str> {
121+
// Debug pins default to their debug function (AF0), everything else
122+
// defaults to floating input.
123+
let mode = match (pin.port()?, pin.number()?) {
124+
('A', 13) | ('A', 14) | ('A', 15) | ('B', 3) | ('B', 4) => "AF0",
125+
_ => "Input<Floating>",
126+
};
127+
Ok(mode)
128+
}
129+
130+
fn get_pin_af_numbers(pin: &gpio::Pin) -> Result<Vec<u8>> {
131+
let mut numbers = Vec::new();
132+
for signal in pin.pin_signals.iter() {
133+
numbers.push(signal.af()?);
134+
}
135+
136+
numbers.sort();
137+
numbers.dedup();
138+
139+
Ok(numbers)
140+
}

codegen/src/codegen/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod gpio;

codegen/src/cubemx/db.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use anyhow::{Context, Result};
2+
use serde::Deserialize;
3+
use std::{
4+
fs::File,
5+
path::{Path, PathBuf},
6+
};
7+
8+
pub struct Db {
9+
root: PathBuf,
10+
}
11+
12+
impl Db {
13+
pub fn new<P: Into<PathBuf>>(root: P) -> Self {
14+
Self { root: root.into() }
15+
}
16+
17+
pub fn load<'de, P: AsRef<Path>, T: Deserialize<'de>>(&self, name: P) -> Result<T> {
18+
let name = name.as_ref();
19+
let mut path = self.root.join(name);
20+
path.set_extension("xml");
21+
22+
let file = File::open(&path).with_context(|| format!("cannot open DB file: {:?}", path))?;
23+
serde_xml_rs::de::from_reader(file)
24+
.with_context(|| format!("cannot parse DB file: {:?}", path))
25+
}
26+
}

codegen/src/cubemx/families.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use crate::cubemx::Db;
2+
use anyhow::{Context, Result};
3+
use serde::Deserialize;
4+
5+
pub fn load(db: &Db) -> Result<Families> {
6+
db.load("families")
7+
}
8+
9+
pub fn load_f3(db: &Db) -> Result<Family> {
10+
load(db)?
11+
.families
12+
.into_iter()
13+
.find(|f| f.name == "STM32F3")
14+
.context("STM32F3 family not found")
15+
}
16+
17+
#[derive(Debug, Deserialize)]
18+
#[serde(rename_all = "PascalCase")]
19+
pub struct Families {
20+
#[serde(rename = "Family")]
21+
pub families: Vec<Family>,
22+
}
23+
24+
#[derive(Debug, Deserialize)]
25+
#[serde(rename_all = "PascalCase")]
26+
pub struct Family {
27+
pub name: String,
28+
#[serde(rename = "SubFamily")]
29+
pub sub_families: Vec<SubFamily>,
30+
}
31+
32+
#[derive(Debug, Deserialize)]
33+
#[serde(rename_all = "PascalCase")]
34+
pub struct SubFamily {
35+
pub name: String,
36+
#[serde(rename = "Mcu")]
37+
pub mcus: Vec<Mcu>,
38+
}
39+
40+
#[derive(Debug, Deserialize)]
41+
#[serde(rename_all = "PascalCase")]
42+
pub struct Mcu {
43+
pub name: String,
44+
}

codegen/src/cubemx/ip/gpio.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use super::ip_path;
2+
use crate::cubemx::Db;
3+
use anyhow::{bail, Context, Result};
4+
use once_cell::sync::Lazy;
5+
use regex::Regex;
6+
use serde::Deserialize;
7+
8+
pub fn load(db: &Db, version: &str) -> Result<Ip> {
9+
let name = format!("GPIO-{}_Modes", version);
10+
db.load(ip_path(&name))
11+
}
12+
13+
#[derive(Debug, Deserialize)]
14+
#[serde(rename_all = "PascalCase")]
15+
pub struct Ip {
16+
pub version: String,
17+
#[serde(rename = "GPIO_Pin")]
18+
pub pins: Vec<Pin>,
19+
}
20+
21+
#[derive(Debug, Deserialize)]
22+
#[serde(rename_all = "PascalCase")]
23+
pub struct Pin {
24+
pub port_name: String,
25+
pub name: String,
26+
#[serde(rename = "PinSignal", default)]
27+
pub pin_signals: Vec<PinSignal>,
28+
}
29+
30+
impl Pin {
31+
pub fn port(&self) -> Result<char> {
32+
static PORT_NAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^P(?P<id>[A-Z])$").unwrap());
33+
34+
let captures = PORT_NAME
35+
.captures(&self.port_name)
36+
.with_context(|| format!("invalid GPIO port name: {}", self.port_name))?;
37+
38+
let id = captures.name("id").unwrap().as_str();
39+
let id = id.parse()?;
40+
Ok(id)
41+
}
42+
43+
pub fn number(&self) -> Result<u8> {
44+
static PIN_NAME: Lazy<Regex> =
45+
Lazy::new(|| Regex::new(r"^P[A-Z](?P<nr>\d{1,2})\b.*$").unwrap());
46+
47+
let captures = PIN_NAME
48+
.captures(&self.name)
49+
.with_context(|| format!("invalid GPIO pin name: {}", self.name))?;
50+
51+
let id = captures.name("nr").unwrap().as_str();
52+
let id = id.parse()?;
53+
Ok(id)
54+
}
55+
}
56+
57+
#[derive(Debug, Deserialize)]
58+
#[serde(rename_all = "PascalCase")]
59+
pub struct PinSignal {
60+
pub name: String,
61+
specific_parameter: SpecificParameter,
62+
}
63+
64+
impl PinSignal {
65+
pub fn af(&self) -> Result<u8> {
66+
let param = &self.specific_parameter;
67+
if param.name == "GPIO_AF" {
68+
parse_af(&param.possible_value)
69+
} else {
70+
bail!("PinSignal is missing a GPIO_AF parameter")
71+
}
72+
}
73+
}
74+
75+
fn parse_af(s: &str) -> Result<u8> {
76+
static AF: Lazy<Regex> = Lazy::new(|| Regex::new(r"^GPIO_AF(?P<nr>\d{1,2})_\w+$").unwrap());
77+
78+
let captures = AF
79+
.captures(s)
80+
.with_context(|| format!("invalid PinSignal AF: {}", s))?;
81+
82+
let nr = captures.name("nr").unwrap().as_str().parse()?;
83+
Ok(nr)
84+
}
85+
86+
#[derive(Debug, Deserialize)]
87+
#[serde(rename_all = "PascalCase")]
88+
struct SpecificParameter {
89+
name: String,
90+
possible_value: String,
91+
}

codegen/src/cubemx/ip/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod gpio;
2+
3+
use std::path::PathBuf;
4+
5+
fn ip_path(name: &str) -> PathBuf {
6+
["IP", name].iter().collect()
7+
}

codegen/src/cubemx/mcu.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use crate::cubemx::Db;
2+
use anyhow::Result;
3+
use serde::Deserialize;
4+
5+
pub fn load(db: &Db, name: &str) -> Result<Mcu> {
6+
db.load(name)
7+
}
8+
9+
#[derive(Debug, Deserialize)]
10+
#[serde(rename_all = "PascalCase")]
11+
pub struct Mcu {
12+
pub ref_name: String,
13+
#[serde(rename = "IP")]
14+
pub ips: Vec<Ip>,
15+
}
16+
17+
#[derive(Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd)]
18+
#[serde(rename_all = "PascalCase")]
19+
pub struct Ip {
20+
pub name: String,
21+
pub version: String,
22+
}

0 commit comments

Comments
 (0)