Skip to content

Commit

Permalink
Merge pull request #129 from ra-kete/gpio-features
Browse files Browse the repository at this point in the history
GPIO features based on codegen from the STM32CubeMX database.
  • Loading branch information
teskje authored Sep 27, 2020
2 parents 4352ea8 + 9b89122 commit 54e8261
Show file tree
Hide file tree
Showing 22 changed files with 1,396 additions and 2,337 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Support for the onboard real-time clock (RTC) ([#136](https://github.com/stm32-rs/stm32f3xx-hal/pull/136))
- Enable DMA for USART on `stm32f302` devices ([#139](https://github.com/stm32-rs/stm32f3xx-hal/pull/139))

### Changed

- Introduced auto-generated GPIO mappings based on the STM32CubeMX database
([#129](https://github.com/stm32-rs/stm32f3xx-hal/pull/129))

## [v0.5.0] - 2020-07-21

### Added
Expand Down
50 changes: 29 additions & 21 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ features = ["stm32f303xc", "rt", "stm32-usbd"]
targets = ["thumbv7em-none-eabihf"]

[dependencies]
cfg-if = "0.1"
cortex-m = "0.6"
cortex-m-rt = "0.6"
embedded-dma = "0.1"
embedded-hal = "0.2"
nb = "0.1"
stm32f3 = "0.11"
paste = "1"
rtcc = "0.2"
stm32f3 = "0.12"

[dependencies.bare-metal]
version = "0.2"
Expand Down Expand Up @@ -53,30 +55,36 @@ device-selected = []
direct-call-deprecated = []
rt = ["stm32f3/rt"]

gpio-f302 = []
gpio-f303 = []
gpio-f303e = []
gpio-f333 = []
gpio-f373 = []

# Any Changes here should be mirrored in README.md, src/lib.rs, and
# .github/workflows/ci.yml.
stm32f301 = ["stm32f3/stm32f301", "device-selected"]
stm32f318 = ["stm32f3/stm32f301", "device-selected"]
stm32f301 = ["gpio-f302", "stm32f3/stm32f301", "device-selected"]
stm32f318 = ["gpio-f302", "stm32f3/stm32f301", "device-selected"]
stm32f302 = ["stm32f3/stm32f302", "direct-call-deprecated"]
stm32f302xb = ["stm32f302", "device-selected"]
stm32f302xc = ["stm32f302", "device-selected"]
stm32f302xd = ["stm32f302", "device-selected"]
stm32f302xe = ["stm32f302", "device-selected"]
stm32f302x6 = ["stm32f302", "device-selected"]
stm32f302x8 = ["stm32f302", "device-selected"]
stm32f302x6 = ["stm32f302", "gpio-f302", "device-selected"]
stm32f302x8 = ["stm32f302", "gpio-f302", "device-selected"]
stm32f302xb = ["stm32f302", "gpio-f303", "device-selected"]
stm32f302xc = ["stm32f302", "gpio-f303", "device-selected"]
stm32f302xd = ["stm32f302", "gpio-f303e", "device-selected"]
stm32f302xe = ["stm32f302", "gpio-f303e", "device-selected"]
stm32f303 = ["stm32f3/stm32f303", "direct-call-deprecated"]
stm32f303xb = ["stm32f303", "stm32-usbd/ram_access_1x16", "device-selected"]
stm32f303xc = ["stm32f303", "stm32-usbd/ram_access_1x16", "device-selected"]
stm32f303xd = ["stm32f303", "stm32-usbd/ram_access_2x16", "device-selected"]
stm32f303xe = ["stm32f303", "stm32-usbd/ram_access_2x16", "device-selected"]
stm32f303x6 = ["stm32f303", "device-selected"]
stm32f303x8 = ["stm32f303", "device-selected"]
stm32f373 = ["stm32f3/stm32f373", "device-selected"]
stm32f378 = ["stm32f3/stm32f373", "device-selected"]
stm32f334 = ["stm32f3/stm32f3x4", "device-selected"]
stm32f328 = ["stm32f3/stm32f3x8", "device-selected"]
stm32f358 = ["stm32f3/stm32f3x8", "device-selected"]
stm32f398 = ["stm32f3/stm32f3x8", "device-selected"]
stm32f303x6 = ["stm32f303", "gpio-f333", "device-selected"]
stm32f303x8 = ["stm32f303", "gpio-f333", "device-selected"]
stm32f303xb = ["stm32f303", "gpio-f303", "stm32-usbd/ram_access_1x16", "device-selected"]
stm32f303xc = ["stm32f303", "gpio-f303", "stm32-usbd/ram_access_1x16", "device-selected"]
stm32f303xd = ["stm32f303", "gpio-f303e", "stm32-usbd/ram_access_2x16", "device-selected"]
stm32f303xe = ["stm32f303", "gpio-f303e", "stm32-usbd/ram_access_2x16", "device-selected"]
stm32f373 = ["gpio-f373", "stm32f3/stm32f373", "device-selected"]
stm32f378 = ["gpio-f373", "stm32f3/stm32f373", "device-selected"]
stm32f334 = ["gpio-f333", "stm32f3/stm32f3x4", "device-selected"]
stm32f328 = ["gpio-f333", "stm32f3/stm32f3x8", "device-selected"]
stm32f358 = ["gpio-f303", "stm32f3/stm32f3x8", "device-selected"]
stm32f398 = ["gpio-f303e", "stm32f3/stm32f3x8", "device-selected"]

[profile.dev]
debug = true
Expand Down
2 changes: 2 additions & 0 deletions codegen/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "x86_64-unknown-linux-gnu"
2 changes: 2 additions & 0 deletions codegen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target/
**/*.rs.bk
19 changes: 19 additions & 0 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "codegen"
version = "0.1.0"
authors = ["Jan Teske <jteske@posteo.net>"]
edition = "2018"

[dependencies]
anyhow = "1"
once_cell = "1"
regex = "1"
serde-xml-rs = "0.4"

[dependencies.structopt]
version = "0.3"
default-features = false

[dependencies.serde]
version = "1"
features = ["derive"]
56 changes: 56 additions & 0 deletions codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Codegen

This crate provides code-generation for the stm32f3xx-hal. It reads information
from an [STM32CubeMX](https://www.st.com/en/development-tools/stm32cubemx.html)
database and uses that to output code that can directly be included into the
source code of the stm32f3xx-hal crate.

For more information on how the STM32CubeMX database is structured, check out
the README in the [cube-parse](https://github.com/dbrgn/cube-parse) repository.

Because by default cargo tries to use the `x86_64-unknown-linux-gnu` target,
when building `codegen`, due to what's specified in the `.cargo/config`, you
need to manually specify your host's target if it differs from that, e.g.:

```
$ cargo run --target x86_64-apple-darwin -- help
```

`codgen` can generate the following code:

- [GPIO mappings](#gpio-mappings)

## GPIO mappings

Running `codegen`'s `gpio` subcommand generates the `gpio!` macro
invocations at the end of `src/gpio.rs`. Re-generating those macro-invocations
is simply a matter of deleting the old ones and then executing:

```
$ cargo run -- gpio $cubemx_db_path >> ../src/gpio.rs
```

`$cubemx_db_path` must be the path to the `db/` directory under an
STM32CubeMX installation. With a default Linux install, this would be
`/opt/stm32cubemx/db`.

The generated `gpio!` invocations are gated by features whose names are derived
from the respective GPIO internal peripheral (IP) version:

- gpio-f302
- gpio-f303
- gpio-f303e
- gpio-f333
- gpio-f373

`codegen` collects those IP versions from the relevant GPIO IP description
files (located at `$cubemx_db_path/mcu/IP/GPIO-*.xml`). The root `<IP>` element
has a `Version` attribute with a value in the form of "STM32Fxxx_gpio_v1_0".
The feature name is constructed by dropping the parts constant between all
version strings and prepending "gpio-".

Note that the GPIO IP version names don't necessarily match the MCUs they are
used in. For example, the GPIOs in `STM32F302xB` MCUs have the IP version
"STM32F303_gpio_v1_0". The MCU features of the `stm32f3xx-hal` also select the
correct `gpio-*` features, so users generally don't have to care about these
details.
141 changes: 141 additions & 0 deletions codegen/src/codegen/gpio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::cubemx::ip::gpio;
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;

struct Port<'a> {
id: char,
pins: Vec<&'a gpio::Pin>,
}

pub fn gen_mappings(gpio_ips: &[gpio::Ip]) -> Result<()> {
for ip in gpio_ips.iter() {
println!();
gen_gpio_ip(ip)?;
}
Ok(())
}

fn gen_gpio_ip(ip: &gpio::Ip) -> Result<()> {
let feature = ip_version_to_feature(&ip.version)?;
let ports = merge_pins_by_port(&ip.pins)?;

println!(r#"#[cfg(feature = "{}")]"#, feature);
gen_gpio_macro_call(&ports, &feature)?;
Ok(())
}

fn ip_version_to_feature(ip_version: &str) -> Result<String> {
static VERSION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^STM32(?P<version>\w+)_gpio_v1_0$").unwrap());

let captures = VERSION
.captures(&ip_version)
.with_context(|| format!("invalid GPIO IP version: {}", ip_version))?;

let version = captures.name("version").unwrap().as_str();
let feature = format!("gpio-{}", version.to_lowercase());
Ok(feature)
}

fn merge_pins_by_port(pins: &[gpio::Pin]) -> Result<Vec<Port>> {
let mut pins_by_port = HashMap::new();
for pin in pins.iter() {
pins_by_port
.entry(pin.port()?)
.and_modify(|e: &mut Vec<_>| e.push(pin))
.or_insert_with(|| vec![pin]);
}

let mut ports = Vec::new();
for (id, mut pins) in pins_by_port {
pins.sort_by_key(|p| p.number().unwrap_or_default());
pins.dedup_by_key(|p| p.number().unwrap_or_default());
ports.push(Port { id, pins });
}
ports.sort_by_key(|p| p.id);

Ok(ports)
}

fn gen_gpio_macro_call(ports: &[Port], feature: &str) -> Result<()> {
println!("gpio!([");
for port in ports {
gen_port(port, feature)?;
}
println!("]);");
Ok(())
}

fn gen_port(port: &Port, feature: &str) -> Result<()> {
let pac_module = get_port_pac_module(port, feature);

println!(" {{");
println!(
" port: ({}/{}, pac: {}),",
port.id,
port.id.to_lowercase(),
pac_module,
);
println!(" pins: [");

for pin in &port.pins {
gen_pin(pin)?;
}

println!(" ],");
println!(" }},");
Ok(())
}

fn get_port_pac_module(port: &Port, feature: &str) -> &'static str {
// The registers in ports A and B have different reset values due to the
// presence of debug pins, so they get dedicated PAC modules.
match port.id {
'A' => "gpioa",
'B' => "gpiob",
'D' if feature == "gpio-f373" => "gpiod",
_ => "gpioc",
}
}

fn gen_pin(pin: &gpio::Pin) -> Result<()> {
let nr = pin.number()?;
let reset_mode = get_pin_reset_mode(pin)?;
let afr = if nr < 8 { 'L' } else { 'H' };
let af_numbers = get_pin_af_numbers(pin)?;

println!(
" {} => {{ reset: {}, afr: {}/{}, af: {:?} }},",
nr,
reset_mode,
afr,
afr.to_lowercase(),
af_numbers,
);

Ok(())
}

fn get_pin_reset_mode(pin: &gpio::Pin) -> Result<&'static str> {
// Debug pins default to their debug function (AF0), everything else
// defaults to floating input.
let mode = match (pin.port()?, pin.number()?) {
('A', 13) | ('A', 14) | ('A', 15) | ('B', 3) | ('B', 4) => "AF0",
_ => "Input<Floating>",
};
Ok(mode)
}

fn get_pin_af_numbers(pin: &gpio::Pin) -> Result<Vec<u8>> {
let mut numbers = Vec::new();
for signal in &pin.pin_signals {
numbers.push(signal.af()?);
}

numbers.sort();
numbers.dedup();

Ok(numbers)
}
11 changes: 11 additions & 0 deletions codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub mod gpio;

use crate::cubemx::package::Package;

pub fn gen_autogen_comment(package: &Package) {
println!("// auto-generated using codegen");
println!(
"// STM32CubeMX DB release: {}",
package.pack_description.release
);
}
33 changes: 33 additions & 0 deletions codegen/src/cubemx/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use anyhow::{Context, Result};
use serde::Deserialize;
use std::{
fs::File,
path::{Path, PathBuf},
};

pub struct Db {
root: PathBuf,
}

impl Db {
pub fn new<P: Into<PathBuf>>(root: P) -> Self {
Self { root: root.into() }
}

pub fn load<'de, P: AsRef<Path>, T: Deserialize<'de>>(&self, name: P) -> Result<T> {
let name = name.as_ref();
let mut path = self.root.join(name);
path.set_extension("xml");

let file = File::open(&path).with_context(|| format!("cannot open DB file: {:?}", path))?;
serde_xml_rs::de::from_reader(file)
.with_context(|| format!("cannot parse DB file: {:?}", path))
}

pub fn load_mcu<'de, P: AsRef<Path>, T: Deserialize<'de>>(&self, name: P) -> Result<T> {
let mut mcu_path = PathBuf::new();
mcu_path.push("mcu");
mcu_path.push(name);
self.load(&mcu_path)
}
}
Loading

0 comments on commit 54e8261

Please sign in to comment.