embassy book

STM32 Developer Documentation

Understanding metapac

When a project imports embassy-stm32 for compilation, it selects the feature corresponding to the chip it uses. Based on this feature, embassy-stm32 selects the IP (Intellectual Property core) supported by the chip and enables the corresponding HAL implementation. But how does embassy-stm32 know which IPs are included in each of the hundreds of chips we support? This is a long story starting from stm32-data-sources.

stm32-data-sources

stm32-data-sources is a rather rudimentary code repository. It has no README, no documentation, and few followers. But it is the core that makes embassy-stm32 possible. The data for each chip we support comes partly from the corresponding XML file, such as STM32F051K4Ux.xml. In this file, you will see lines like this:

    <IP InstanceName="I2C1" Name="I2C" Version="i2c2_v1_1_Cube"/>
    <IP ConfigFile="TIM-STM32F0xx" InstanceName="TIM1" Name="TIM1_8F0" Version="gptimer2_v2_x_Cube"/>

These lines indicate that the chip has an i2c with version "v1_1". It also indicates that the chip has a general-purpose timer with version "v2_x". With this data, we can determine which implementations should be included in embassy-stm32. But actually performing this process is another matter.

stm32-data

Although all users of this project are familiar with embassy-stm32, few people know about the project that supports it: stm32-data. This project is not just for generating data for embassy-stm32, but for making this data universally usable by machines. To achieve this goal, information from multiple files in the stm32-data-sources project is combined and parsed to assign register block implementations for each supported IP. The core of this matching is located in chips.rs:

    (".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")),
    (r".*TIM\\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")),

In this example, 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 these IPs respectively. The result is that these lines are generated in STM32F051K4.json:

    {
        "name": "I2C1",
        "address": 1073763328,
        "registers": {
            "kind": "i2c",
            "version": "v2",
            "block": "I2C"
        },
        // 省略
    }
    // 省略
    {
        "name": "TIM1",
        "address": 1073818624,
        "registers": {
            "kind": "timer",
            "version": "v1",
            "block": "TIM_ADV"
        },
        // 省略
    }

In addition to register blocks, pin and RCC mapping data are also generated and used by embassy-stm32. stm32-metapac-gen is used to package and publish the data as a crate.

embassy-stm32

In the lib.rs file in the root directory of embassy-stm32, you will see lines like this:

#[cfg(i2c)]
pub mod i2c;

And in the mod.rs of the i2c module, you will see:

#[cfg_attr(i2c_v2, path = "v2.rs")]

Because STM32F051K4 supports i2c, and its version corresponds to our "v2", the i2c and i2c_v2 configuration directives will exist, and embassy-stm32 will include these files accordingly. These configuration directives and tables are generated from chip data, enabling embassy-stm32 to clearly and explicitly adjust the logic and implementation according to the needs of each chip. Compared to other projects in the embedded ecosystem, embassy-stm32 is the only project that can reuse code across the entire STM32 product line and remove difficult-to-implement insecure logic to the HAL.