开发者文档:STM32

理解 metapac

当一个项目引入 embassy-stm32 包并进行编译时,它会选择与项目使用的芯片相对应的feature。基于这个feature,embassy-stm32 会选出该芯片支持的 IP ,并启用相应的HAL实现。但是 embassy-stm32 如何知道芯片包含哪些IP呢?这是一个很长的故事,让我们从 stm32-data-sources 开始讲起。

stm32-data-sources

stm32-data-sources 是一个几乎空白的仓库。它没有README,没有文档,watcher也很少。但它使 embassy-stm32 成为可能。我们支持的芯片的部分数据来自于相应的XML文件,例如 STM32F051K4Ux.xml 。在该文件中,您会看到类似于下面的内容:

    <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"/>

这些内容表明这个芯片有一个i2c,其版本为 “v1_1”。它还表明它有一个版本为 “v2_x” 的通用定时器。根据这些数据,我们可以确定要在 embassy-stm32 中包含哪些实现。但实际上,做到这一点又是另外一回事。

stm32-data

虽然所有使用这个项目的用户都熟悉 embassy-stm32 ,但较少人熟悉驱动它的项目:stm32-data 。这个项目的目标是以机器化的方式为 embassy-stm32 生成数据。为了实现这一点,我们合并与解析了 stm32-data-sources 项目中的多个文件的信息,以给每个支持的IP分配寄存器块实现。这种匹配的核心机制位于 chips.rs

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

在这种情况下,i2c的版本对应我们的 “v2”,通用定时器的版本对应我们的 “v1”。因此, i2c_v2.yamltimer_v1.yaml 寄存器块实现分别被分配给这些IP。于是,在 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
    }

除了寄存器块,它还生成了引脚映射和RCC映射的数据,并被 embassy-stm32 使用。stm32-metapac-gen 打包数据,并作为一个crate发布。

embassy-stm32

embassy-stm32 根目录中的 lib.rs 文件中,您会看到这样一行:

#[cfg(i2c)]
pub mod i2c;

在i2c模块的 mod.rs 您会看到这样一行:

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

因为STM32F051K4支持i2c,其版本对应我们的 “v2”,所以将会出现 i2c 和 i2c_v2 配置指令, embassy-stm32 会分别包含这些文件。它们和其他配置指令以及表格是根据芯片数据生成的,因此 embassy-stm32 可以清晰地满足每个芯片所需的逻辑和实现。与嵌入式生态中的其他项目相比, embassy-stm32 是唯一一个能够在stm32所有系列中复用代码,并移除HAL中难以实现的不安全逻辑的项目。