-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #129 from ra-kete/gpio-features
GPIO features based on codegen from the STM32CubeMX database.
- Loading branch information
Showing
22 changed files
with
1,396 additions
and
2,337 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[build] | ||
target = "x86_64-unknown-linux-gnu" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target/ | ||
**/*.rs.bk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.