Skip to content

Commit 54e8261

Browse files
authored
Merge pull request #129 from ra-kete/gpio-features
GPIO features based on codegen from the STM32CubeMX database.
2 parents 4352ea8 + 9b89122 commit 54e8261

22 files changed

+1396
-2337
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1515
- Support for the onboard real-time clock (RTC) ([#136](https://github.com/stm32-rs/stm32f3xx-hal/pull/136))
1616
- Enable DMA for USART on `stm32f302` devices ([#139](https://github.com/stm32-rs/stm32f3xx-hal/pull/139))
1717

18+
### Changed
19+
20+
- Introduced auto-generated GPIO mappings based on the STM32CubeMX database
21+
([#129](https://github.com/stm32-rs/stm32f3xx-hal/pull/129))
22+
1823
## [v0.5.0] - 2020-07-21
1924

2025
### Added

Cargo.toml

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ features = ["stm32f303xc", "rt", "stm32-usbd"]
1616
targets = ["thumbv7em-none-eabihf"]
1717

1818
[dependencies]
19+
cfg-if = "0.1"
1920
cortex-m = "0.6"
2021
cortex-m-rt = "0.6"
2122
embedded-dma = "0.1"
2223
embedded-hal = "0.2"
2324
nb = "0.1"
24-
stm32f3 = "0.11"
25+
paste = "1"
2526
rtcc = "0.2"
27+
stm32f3 = "0.12"
2628

2729
[dependencies.bare-metal]
2830
version = "0.2"
@@ -53,30 +55,36 @@ device-selected = []
5355
direct-call-deprecated = []
5456
rt = ["stm32f3/rt"]
5557

58+
gpio-f302 = []
59+
gpio-f303 = []
60+
gpio-f303e = []
61+
gpio-f333 = []
62+
gpio-f373 = []
63+
5664
# Any Changes here should be mirrored in README.md, src/lib.rs, and
5765
# .github/workflows/ci.yml.
58-
stm32f301 = ["stm32f3/stm32f301", "device-selected"]
59-
stm32f318 = ["stm32f3/stm32f301", "device-selected"]
66+
stm32f301 = ["gpio-f302", "stm32f3/stm32f301", "device-selected"]
67+
stm32f318 = ["gpio-f302", "stm32f3/stm32f301", "device-selected"]
6068
stm32f302 = ["stm32f3/stm32f302", "direct-call-deprecated"]
61-
stm32f302xb = ["stm32f302", "device-selected"]
62-
stm32f302xc = ["stm32f302", "device-selected"]
63-
stm32f302xd = ["stm32f302", "device-selected"]
64-
stm32f302xe = ["stm32f302", "device-selected"]
65-
stm32f302x6 = ["stm32f302", "device-selected"]
66-
stm32f302x8 = ["stm32f302", "device-selected"]
69+
stm32f302x6 = ["stm32f302", "gpio-f302", "device-selected"]
70+
stm32f302x8 = ["stm32f302", "gpio-f302", "device-selected"]
71+
stm32f302xb = ["stm32f302", "gpio-f303", "device-selected"]
72+
stm32f302xc = ["stm32f302", "gpio-f303", "device-selected"]
73+
stm32f302xd = ["stm32f302", "gpio-f303e", "device-selected"]
74+
stm32f302xe = ["stm32f302", "gpio-f303e", "device-selected"]
6775
stm32f303 = ["stm32f3/stm32f303", "direct-call-deprecated"]
68-
stm32f303xb = ["stm32f303", "stm32-usbd/ram_access_1x16", "device-selected"]
69-
stm32f303xc = ["stm32f303", "stm32-usbd/ram_access_1x16", "device-selected"]
70-
stm32f303xd = ["stm32f303", "stm32-usbd/ram_access_2x16", "device-selected"]
71-
stm32f303xe = ["stm32f303", "stm32-usbd/ram_access_2x16", "device-selected"]
72-
stm32f303x6 = ["stm32f303", "device-selected"]
73-
stm32f303x8 = ["stm32f303", "device-selected"]
74-
stm32f373 = ["stm32f3/stm32f373", "device-selected"]
75-
stm32f378 = ["stm32f3/stm32f373", "device-selected"]
76-
stm32f334 = ["stm32f3/stm32f3x4", "device-selected"]
77-
stm32f328 = ["stm32f3/stm32f3x8", "device-selected"]
78-
stm32f358 = ["stm32f3/stm32f3x8", "device-selected"]
79-
stm32f398 = ["stm32f3/stm32f3x8", "device-selected"]
76+
stm32f303x6 = ["stm32f303", "gpio-f333", "device-selected"]
77+
stm32f303x8 = ["stm32f303", "gpio-f333", "device-selected"]
78+
stm32f303xb = ["stm32f303", "gpio-f303", "stm32-usbd/ram_access_1x16", "device-selected"]
79+
stm32f303xc = ["stm32f303", "gpio-f303", "stm32-usbd/ram_access_1x16", "device-selected"]
80+
stm32f303xd = ["stm32f303", "gpio-f303e", "stm32-usbd/ram_access_2x16", "device-selected"]
81+
stm32f303xe = ["stm32f303", "gpio-f303e", "stm32-usbd/ram_access_2x16", "device-selected"]
82+
stm32f373 = ["gpio-f373", "stm32f3/stm32f373", "device-selected"]
83+
stm32f378 = ["gpio-f373", "stm32f3/stm32f373", "device-selected"]
84+
stm32f334 = ["gpio-f333", "stm32f3/stm32f3x4", "device-selected"]
85+
stm32f328 = ["gpio-f333", "stm32f3/stm32f3x8", "device-selected"]
86+
stm32f358 = ["gpio-f303", "stm32f3/stm32f3x8", "device-selected"]
87+
stm32f398 = ["gpio-f303e", "stm32f3/stm32f3x8", "device-selected"]
8088

8189
[profile.dev]
8290
debug = true

codegen/.cargo/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "x86_64-unknown-linux-gnu"

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 <[email protected]>"]
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.structopt]
14+
version = "0.3"
15+
default-features = false
16+
17+
[dependencies.serde]
18+
version = "1"
19+
features = ["derive"]

codegen/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Codegen
2+
3+
This crate provides code-generation for the stm32f3xx-hal. It reads information
4+
from an [STM32CubeMX](https://www.st.com/en/development-tools/stm32cubemx.html)
5+
database and uses that to output code that can directly be included into the
6+
source code of the stm32f3xx-hal crate.
7+
8+
For more information on how the STM32CubeMX database is structured, check out
9+
the README in the [cube-parse](https://github.com/dbrgn/cube-parse) repository.
10+
11+
Because by default cargo tries to use the `x86_64-unknown-linux-gnu` target,
12+
when building `codegen`, due to what's specified in the `.cargo/config`, you
13+
need to manually specify your host's target if it differs from that, e.g.:
14+
15+
```
16+
$ cargo run --target x86_64-apple-darwin -- help
17+
```
18+
19+
`codgen` can generate the following code:
20+
21+
- [GPIO mappings](#gpio-mappings)
22+
23+
## GPIO mappings
24+
25+
Running `codegen`'s `gpio` subcommand generates the `gpio!` macro
26+
invocations at the end of `src/gpio.rs`. Re-generating those macro-invocations
27+
is simply a matter of deleting the old ones and then executing:
28+
29+
```
30+
$ cargo run -- gpio $cubemx_db_path >> ../src/gpio.rs
31+
```
32+
33+
`$cubemx_db_path` must be the path to the `db/` directory under an
34+
STM32CubeMX installation. With a default Linux install, this would be
35+
`/opt/stm32cubemx/db`.
36+
37+
The generated `gpio!` invocations are gated by features whose names are derived
38+
from the respective GPIO internal peripheral (IP) version:
39+
40+
- gpio-f302
41+
- gpio-f303
42+
- gpio-f303e
43+
- gpio-f333
44+
- gpio-f373
45+
46+
`codegen` collects those IP versions from the relevant GPIO IP description
47+
files (located at `$cubemx_db_path/mcu/IP/GPIO-*.xml`). The root `<IP>` element
48+
has a `Version` attribute with a value in the form of "STM32Fxxx_gpio_v1_0".
49+
The feature name is constructed by dropping the parts constant between all
50+
version strings and prepending "gpio-".
51+
52+
Note that the GPIO IP version names don't necessarily match the MCUs they are
53+
used in. For example, the GPIOs in `STM32F302xB` MCUs have the IP version
54+
"STM32F303_gpio_v1_0". The MCU features of the `stm32f3xx-hal` also select the
55+
correct `gpio-*` features, so users generally don't have to care about these
56+
details.

codegen/src/codegen/gpio.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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, &feature)?;
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], feature: &str) -> Result<()> {
63+
println!("gpio!([");
64+
for port in ports {
65+
gen_port(port, feature)?;
66+
}
67+
println!("]);");
68+
Ok(())
69+
}
70+
71+
fn gen_port(port: &Port, feature: &str) -> Result<()> {
72+
let pac_module = get_port_pac_module(port, feature);
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 {
84+
gen_pin(pin)?;
85+
}
86+
87+
println!(" ],");
88+
println!(" }},");
89+
Ok(())
90+
}
91+
92+
fn get_port_pac_module(port: &Port, feature: &str) -> &'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+
'D' if feature == "gpio-f373" => "gpiod",
99+
_ => "gpioc",
100+
}
101+
}
102+
103+
fn gen_pin(pin: &gpio::Pin) -> Result<()> {
104+
let nr = pin.number()?;
105+
let reset_mode = get_pin_reset_mode(pin)?;
106+
let afr = if nr < 8 { 'L' } else { 'H' };
107+
let af_numbers = get_pin_af_numbers(pin)?;
108+
109+
println!(
110+
" {} => {{ reset: {}, afr: {}/{}, af: {:?} }},",
111+
nr,
112+
reset_mode,
113+
afr,
114+
afr.to_lowercase(),
115+
af_numbers,
116+
);
117+
118+
Ok(())
119+
}
120+
121+
fn get_pin_reset_mode(pin: &gpio::Pin) -> Result<&'static str> {
122+
// Debug pins default to their debug function (AF0), everything else
123+
// defaults to floating input.
124+
let mode = match (pin.port()?, pin.number()?) {
125+
('A', 13) | ('A', 14) | ('A', 15) | ('B', 3) | ('B', 4) => "AF0",
126+
_ => "Input<Floating>",
127+
};
128+
Ok(mode)
129+
}
130+
131+
fn get_pin_af_numbers(pin: &gpio::Pin) -> Result<Vec<u8>> {
132+
let mut numbers = Vec::new();
133+
for signal in &pin.pin_signals {
134+
numbers.push(signal.af()?);
135+
}
136+
137+
numbers.sort();
138+
numbers.dedup();
139+
140+
Ok(numbers)
141+
}

codegen/src/codegen/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pub mod gpio;
2+
3+
use crate::cubemx::package::Package;
4+
5+
pub fn gen_autogen_comment(package: &Package) {
6+
println!("// auto-generated using codegen");
7+
println!(
8+
"// STM32CubeMX DB release: {}",
9+
package.pack_description.release
10+
);
11+
}

codegen/src/cubemx/db.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
27+
pub fn load_mcu<'de, P: AsRef<Path>, T: Deserialize<'de>>(&self, name: P) -> Result<T> {
28+
let mut mcu_path = PathBuf::new();
29+
mcu_path.push("mcu");
30+
mcu_path.push(name);
31+
self.load(&mcu_path)
32+
}
33+
}

0 commit comments

Comments
 (0)