开发者文档: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.yaml
和 timer_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中难以实现的不安全逻辑的项目。