embassy book

STM32开发者文档

理解metapac

当一个项目导入embassy-stm32进行编译时,该项目会选择与其使用的芯片相对应的特性。基于这个特性,embassy-stm32会选择该芯片支持的IP(知识产权核),并启用相应的HAL实现。但是,embassy-stm32是如何知道在我们支持的数百种芯片中,每个芯片包含哪些IP呢?这是一个从stm32-data-sources开始的漫长故事。

stm32-data-sources

stm32-data-sources是一个相当简陋的代码仓库。它没有README,没有文档,关注者也很少。但它是使embassy-stm32成为可能的核心。我们支持的每个芯片的数据都部分来自于对应的XML文件,比如STM32F051K4Ux.xml。在该文件中,你会看到如下这样的行:

    <IP InstanceName="I2C1" Name="I2C" Version="i2c2_v1_1_Cube"/>
    <!-- 省略  -->
    <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")),
    // 省略
    (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"
        },
        // 省略
    }
    // 省略
    {
        "name": "TIM1",
        "address": 1073818624,
        "registers": {
            "kind": "timer",
            "version": "v1",
            "block": "TIM_ADV"
        },
        // 省略
    }

除了寄存器块外,引脚和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",所以i2ci2c_v2配置指令将会存在,embassy-stm32会相应地包含这些文件。这些配置指令和表格是从芯片数据生成的,使embassy-stm32能够清晰明了地根据每个芯片的需求调整逻辑和实现。与嵌入式生态系统中的其他项目相比,embassy-stm32是唯一一个能够在整个STM32产品线中重用代码,并将难以实现的不安全逻辑移除到HAL的项目。