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