Developer Documentation: STM32
Understanding metapac
When a project that imports embassy-stm32
is compiled, that project selects the feature corresponding to the chip that project is using. Based on that feature, embassy-stm32
selects supported IP for the chip, and enables the corresponding HAL implementations. But how does embassy-stm32
know what IP the chip contains, out of the hundreds of chips that we support? It’s a long story that starts with stm32-data-sources
.
stm32-data-sources
stm32-data-sources
is as mostly barren repository. It has no README, no documentation, and few watchers. But it’s the core of what makes embassy-stm32
possible. The data for every chip that we support is taken in part from a corresponding XML file like STM32F051K4Ux.xml
. In that file, you’ll see lines like the following:
<IP InstanceName="I2C1" Name="I2C" Version="i2c2_v1_1_Cube"/>
<!-- snip -->
<IP ConfigFile="TIM-STM32F0xx" InstanceName="TIM1" Name="TIM1_8F0" Version="gptimer2_v2_x_Cube"/>
These lines indicate that this chip has an i2c, and that it’s version is "v1_1". It also indicates that it has a general purpose timer that with a version of "v2_x". From this data, it’s possible to determine which implementations should be included in embassy-stm32
. But actually doing that is another matter.
stm32-data
While all users of this project are familiar with embassy-stm32
, fewer are familiar with the project that powers it: stm32-data
. This project doesn’t just aim to generate data for embassy-stm32
, but for machine consumption in general. To acheive this, information from multiple files from the stm32-data-sources
project are combined and parsed to assign register block implementations for each supported IP. The core of this matching resides in chips.rs
:
(".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")),
// snip
(r".*TIM\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")),
In this case, the i2c version corresponds to our "v2" and the general purpose timer version corresponds to our "v1". Therefore, the i2c_v2.yaml
and timer_v1.yaml
register block implementations are assigned to those IP, respectively. The result is that these lines arr generated in STM32F051K4.json
:
{
"name": "I2C1",
"address": 1073763328,
"registers": {
"kind": "i2c",
"version": "v2",
"block": "I2C"
},
// snip
}
// snip
{
"name": "TIM1",
"address": 1073818624,
"registers": {
"kind": "timer",
"version": "v1",
"block": "TIM_ADV"
},
// snip
}
In addition to register blocks, data for pin and RCC mapping is also generated and consumed by embassy-stm32
. stm32-metapac-gen
is used to package and publish the data as a crate.
embassy-stm32
In the lib.rs
file located in the root of embassy-stm32
, you’ll see this line:
#[cfg(i2c)]
pub mod i2c;
And in the mod.rs
of the i2c mod, you’ll see this:
#[cfg_attr(i2c_v2, path = "v2.rs")]
Because i2c is supported for STM32F051K4 and its version corresponds to our "v2", the i2c
and i2c_v2
, configuration directives will be present, and embassy-stm32
will include these files, respectively. This and other configuration directives and tables are generated from the data for chip, allowing embassy-stm32
to expressively and clearly adapt logic and implementations to what is required for each chip. Compared to other projects across the embedded ecosystem, embassy-stm32
is the only project that can re-use code across the entire stm32 lineup and remove difficult-to-implement unsafe logic to the HAL.