Skip to content

GPIO features #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Sep 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>"]
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