Omar Hiari 2023-01-31
引言
在开始使用嵌入式Rust时,我曾经天真地认为所有现有的硬件抽象层(HALs)都或多或少采用相同的方法。也许更接近于你在其他语言中看到的东西。不久之后,我意识到自己错了。实际上,对于初学者来说,这可能更加令人困惑。选择一个开始使用的HAL变得非常困难。特别是对于那些没有太多嵌入式背景的人来说。
在这篇文章中,我尝试对目前存在的不同HAL进行分类和解释它们之间的差异。随着嵌入式Rust领域的不断演变,这篇文章并不期望能永远保持最新。我只是认为任何刚开始的人都希望能够更好地了解那里存在的不同选项,然后再挑选一个HAL。
Embedded-HAL不是一个真正的HAL 🤨
好吧,至少不是通常意义上的HAL所做的事情。因此,命名成为了一个巨大的混淆来源,澄清这一点非常重要。可以将embedded-hal视为位于现有HAL crate之上的crate,通过trait定义共同行为。这意味着embedded-hal不能也不作为独立的HAL运行,而是被现有的设备HAL采纳以定义共同行为。这是一个非常强大的概念,因为它能够创建平台无关的驱动程序。
接下来是更多内容。
HAL的能力 🧰
不同的HAL提供不同的特性/能力。这些能力包括:
设备支持级别
理想情况下,人们希望有一个HAL支持所有控制器。这意味着一个代码库可以在所有设备之间移植。显然,情况并非如此,尽管有些HAL的支持级别比其他的更高。例如,有些HAL支持整个系列的控制器(如STM32),只支持系列中的一个家族(如STM32F1xx),甚至只支持单个设备。
类型状态采纳
某些HAL利用Rust类型系统管理引脚配置并在编译时检查引脚兼容性。这是个不错的功能,可以确保你正确配置引脚。例如,如果尝试将UART外设连接到不支持UART的引脚上,编译器会生成错误。
我个人认为类型状态的采纳可能是初学者的理想特征。如果没有类型状态,错误配置引脚可能会导致初学者经历沮丧的调试过程。
embedded-hal支持
这涉及到对embedded-hal trait的支持程度。支持的trait越多,HAL的兼容性就越好。
std支持
大多数情况下,由于资源限制,嵌入式Rust HAL不支持std库。然而,一些具有更多资源和高级功能(如网络或无线)的设备需要std支持。
其他考虑因素 🛠
文档
不用说,HAL的文档质量是一个非常重要的方面。有许多API描述不佳或者签名过时的情况会让你感到困惑。
易用性
这可能意味着几件事情,它全部指向学习曲线的陡峭程度,尤其是在Rust中。一个是API的友好程度。另一个是代码可能会有多冗长。最后,实现像中断这样的东西有多复杂?
API覆盖范围
这与HAL支持设备特性的程度有关。意思是说,某些HAL不一定实现了控制器(或控制器家族)的所有特性。这将导致一个人可能不得不依赖不同的crate(例如PAC-level crate)来实现特定的实现。
对HAL进行分类 📗
在下表中,我根据前面提到的参数比较了一些我遇到的HAL。虽然表格并不详尽,但大多数HAL都属于类似的类别,将在下面讨论。
| Crate示例 | esp-hal变体 | esp-idf-hal | Embassy HALs (Ex. embassy-nrf, embassy-stm, & embassy-rp) | STM32-HAL 和 nRF-HAL | 各种STM32 HAL (Ex. stm32f4xx-hal, stm32f1xx-hal...等) | nRF设备HAL |
|---|---|---|---|---|---|---|
| 设备支持级别 | 家族支持通过单独的HALs (esp32-hal, esp32c2-hal...等) | 系列支持 (各种ESP32变体) | 系列支持 | 家族支持 | 家族和支持级别取决于Crate | 家族和设备级支持取决于Crate |
| 采纳类型状态 | 是 | 是 | 不,但在另一种方式下仍确保正确的引脚配置。 | 不 | 是 | 是 |
| embedded-hal风格API | 大部分是。 | 主要用于低级硬件访问。 | 不采用embedded-hal风格API,但是支持embedded-hal集成。 | 不采用embedded-hal风格API,但是支持embedded-hal集成。 | 是。在同一系列的不同crate中有小的变化。 | 是。在同一系列的不同crate中有小的变化。 |
| std支持 | 否 | 是 | 否 | 否 | 否 | 否 |
| 文档 | Espressif提供的文档很好。 | Espressif提供的文档很好。 | 在某些领域可能已经过时。有时需要参考源代码。 | 并非所有变体的所有方面都是准确的。 | 取决于Crate | 取决于Crate |
| 易用性 | 处理中断和DMA可能会很困难。代码可能相对冗长。 | 需要一种不同于其他HAL的方法。学习曲线可能相当陡峭。设置可能更为复杂。 | 非常易于使用的友好API。代码不冗长。需要进入异步处理多线程。 | 真正友好的API。消除了基于trait的HAL的许多烦恼。 | 处理中断和DMA可能会很困难。从PAC结构提升到HAL可能会令人困惑。没有RTIC框架的情况下代码可能相当冗长。 | 处理中断和DMA可能会很困难。从PAC结构提升到HAL可能会令人困惑。没有RTIC框架的情况下代码可能相当冗长。 |
| API覆盖范围 | 覆盖范围非常好。不同ESP HAL之间有很大的一致性。 | 覆盖范围非常好。 | nRF HAL可能是最完整的。可能会发现缺少的实现。 | STM设备的覆盖范围优于nRF。 | 取决于Crate | 取决于Crate |
从表格中可以看出,基于Rust的HAL似乎都落入了四种类别:
- 基于embedded-hal trait的HAL:可能有更好的描述,但这一类拥有最广泛的实现基础,并且有比这里提到更多的选项。更全面的列表可以在awesome embedded Rust仓库中找到。
- Embassy HALs:当前HAL仅支持stm32、rp和nRF。
- 带有std支持的HAL:目前仅限于ESP32设备。
- 无类型状态的HAL:这是为了更好的人体工程学而牺牲的,正如作者所说。现在只有两个HAL属于此类别,即STM32-HAL和nRF-HAL。
我应该走哪条路线?🤔
这是价值百万美元的问题。我经常思考如果我要重新做一切,我会不会选择相同的路线?下面我分析了不同的路线,并给出了个人意见。
The Embassy Route
Embassy拥有最友好的API和配置非常简单。即使是设置中断和DMA也非常直接。然而,在某个时刻,必须涉及async。这不是坏事,相反,向前发展采用它可能是更好的。然而,就个人喜好而言,如果从嵌入式开始,我会避免任何底层框架。我喜欢理解如何直接与控制器交互,没有任何中介。
基于embedded-hal Trait的HAL路线
虽然这个路线的API不像embassy那样友好,而且某些东西比如中断可能会有点痛苦,但我认为仍然值得选择。这是我个人选择的路线,它帮助我更好地理解了一些概念。不过,这条路线的选择很多。挑战在于选择一个具有良好支持和文档的HAL。显然,这对初学者来说不容易弄清楚。现在的好消息是围绕着Espressif官方支持的ESP HAL正在迅速成长的生态系统。
在我开始的时候,Espressif对ESP HAL的支持还不存在。然而,如果我现在开始,肯定会选择其中一个ESP HAL。
带有std支持的HAL路线
由于与我会在embassy中避免的一些原因相同,我会在开始时避开这条路线。再加上API不像embassy那么友好,代码可能会相当冗长。
无类型状态的路线
对于这类存在的HAL,好处是它们相对容易使用。不过,我认为类型状态会对初学者有所帮助,不会自找麻烦。此外,它引入了Rust中的一个不错特性,这是在其他常用语言(如C)中不可用的。最后,这类HAL似乎不如其他HAL流行。
结论
在嵌入式Rust的HAL空间中导航可能是一次艰难的经历。这篇文章分析了Rust HAL空间并查看了存在的不同选项。