rust嵌入式开发零基础入门教程(二)
本教程的第二部分,我们将深入理解 Rust 语言的核心概念——所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)。这些是 Rust 内存安全的基础,也是初学者理解 Rust 最关键的部分。理解它们后,我们还将探讨如何准备一块实际的开发板,为真正点亮 LED 做准备。
4. Rust 核心概念:所有权、借用和生命周期
Rust 最大的特点就是它的内存安全模型,它在编译时而不是运行时进行内存管理,从而避免了 C/C++ 中常见的内存错误,如空指针引用、数据竞争等。这主要通过三个核心概念实现:所有权、借用和生命周期。
4.1 所有权 (Ownership)
所有权是 Rust 的核心特性。 每个值在 Rust 中都有一个所有者 (owner)。
规则 1: 每个值都有且只有一个所有者。
规则 2: 当所有者超出其作用域 (scope) 时,值会被丢弃 (dropped),其内存也会被自动回收。
示例:
fn main() {let s1 = String::from("hello"); // s1 拥有 "hello" 这个字符串数据let s2 = s1; // 这里发生了“移动”(move),s1 的所有权被转移给了 s2// println!("{}", s1); // 错误!s1 已经不再拥有数据了,会报错:value borrowed here after moveprintln!("{}", s2); // 正确,s2 现在是数据的所有者
} // s2 超出作用域,"hello" 内存被释放
在嵌入式开发中,这意味着你不再需要手动调用 free()
或 delete
,也不用担心忘记释放内存而导致内存泄漏。Rust 编译器会在编译时替你处理好这一切。
4.2 借用 (Borrowing)
如果你不想转移所有权,但又需要使用某个值,该怎么办?这就是借用的作用。你可以通过引用 (&
) 来借用一个值,而不是转移它的所有权。
规则 1: 在任意给定时间,你只能拥有一个可变引用 (
&mut T
) 或任意数量的不可变引用 (&T
)。规则 2: 引用必须总是有效的。
示例:
fn main() {let mut s = String::from("hello"); // s 是可变的 String// 不可变借用:可以有多个let r1 = &s; // r1 借用了 slet r2 = &s; // r2 也借用了 sprintln!("{} and {}", r1, r2); // 正确,可以同时使用多个不可变引用// s 和 r1, r2 不再被使用后,这些不可变借用结束// 可变借用:只能有一个let r3 = &mut s; // r3 可变地借用了 sr3.push_str(", world!"); // 可以通过 r3 修改 s 的值println!("{}", r3); // 正确// println!("{}", s); // 错误!当 r3 还在活跃时,不能再使用 s,因为可变借用是独占的。// 如果这里需要使用 s,r3 必须先不再被使用或超出作用域。
}
在嵌入式开发中,借用规则对于硬件寄存器访问和**共享资源(如外设)**的管理至关重要。它能防止你在同一时间对同一个寄存器进行不安全的并发修改,或者在读取的同时又尝试写入。
4.3 生命周期 (Lifetimes)
生命周期是 Rust 编译器用来确保所有借用都是有效的机制。 它们表示引用能够保持有效的作用域。
规则: 引用的生命周期不能超过其所引用值的生命周期。
示例:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}fn main() {let string1 = String::from("abcd");let string2 = "xyz"; // 字面量 'static 生命周期// 这里,longest 函数返回的引用,其生命周期会是 string1 和 string2 中较短的那个。// 'a 标注确保了编译器在编译时检查引用的有效性。let result = longest(&string1, &string2);println!("The longest string is {}", result);
}
在嵌入式开发中,生命周期通常在处理外设驱动程序或裸机内存区域时显得尤为重要。例如,如果你有一个引用指向某个外设寄存器,生命周期会确保这个引用在寄存器被释放或重置之前一直是有效的,从而避免访问已经不存在的内存。
5. 准备你的实际开发板
现在你对 Rust 的核心概念有了基本理解,是时候准备一块真实的微控制器板,让你的 Rust 代码在硬件上跑起来了!
5.1 选择一块开发板
对于初学者,我强烈推荐使用一个内置调试器的开发板,例如:
STM32 Nucleo 系列: (推荐,价格适中,资料丰富,有内置 ST-Link 调试器)
例如:STM32F401RE Nucleo-64 或 STM32F411RE Nucleo-64。它们通常基于 Cortex-M4 处理器。
STM32 Discovery 系列: 功能更强大,也通常内置调试器。
Microbit (V2): 尽管它内置调试器(DAPLink),但其 Cortex-M4F 处理器相对较小,且生态系统更偏向教育。
Raspberry Pi Pico: 基于 RP2040 (双 Cortex-M0+),价格非常便宜,内置调试(DAPLink)支持,生态也发展迅速。
购买建议: 如果你还没有开发板,**一块带有 ST-Link 或 DAPLink 调试器的 STM32 Nucleo/Discovery 板是一个非常好的起点。**它们可以直接通过 USB 连接到电脑进行烧录和调试,无需额外购买调试器。
5.2 安装板级工具和驱动
一旦你有了开发板,你需要安装一些工具和驱动程序,以便你的电脑能与开发板通信。
安装板级驱动:
ST-Link 驱动 (针对 STM32 Nucleo/Discovery): 访问 STMicroelectronics 官网,搜索并下载安装 ST-Link 驱动。
Windows 用户通常需要安装驱动。
Linux 和 macOS 通常自带
libusb
,但可能需要安装额外的 udev 规则来允许非 root 用户访问 USB 设备(具体请搜索 "ST-Link udev rules Linux")。
DAPLink / J-Link 驱动 (如果你使用 Microbit V2 或其他): DAPLink 通常是免驱动的,因为它模拟了一个 USB 大容量存储设备。J-Link 需要安装 SEGGER 提供的驱动。
安装
openocd
(开放片上调试器):openocd
是一个用于调试和烧录微控制器的开源工具。Linux (Debian/Ubuntu):
sudo apt install openocd
macOS (Homebrew):
brew install openocd
Windows: 从 OpenOCD 的 GitHub 页面或官方下载渠道获取预编译的二进制文件,并将其路径添加到系统环境变量
PATH
中。
验证
openocd
安装: 将你的开发板通过 USB 线连接到电脑,然后在终端中运行:openocd --version
如果显示版本号,说明安装成功。要测试它是否能识别你的板子,你可以尝试运行针对你板子的配置命令(例如:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
,具体配置取决于你的板子型号)。
5.3 烧录和调试工具的额外配置 (可选但推荐)
在 第一部分 我们安装了 probe-run
,它是一个方便的工具,可以结合 openocd
和 GDB
来直接烧录和运行 Rust 嵌入式程序。
确保 .cargo/config.toml
文件中配置了 runner
。如果你使用了 cortex-m-quickstart
模板,这个文件通常已经配置好了,类似这样:
Ini, TOML
# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf" # 或者你的目标[target.thumbv7em-none-eabihf] # 或者你的目标
runner = "probe-run --chip STM32F401RETx" # 替换为你的芯片型号# 例如:STM32F411RETx, nRF52840 etc.
如何找到你的芯片型号? 通常印在微控制器芯片本体上,或者查看开发板的说明书。例如,STM32F401RE Nucleo-64 板上是 STM32F401RET6。你只需要提供前缀,例如 STM32F401RETx
。
下一步
现在,你已经理解了 Rust 的核心内存安全概念,并且准备好了你的实际开发板和必要的工具。
在 第三部分,我们将编写一个真正的嵌入式 "Hello, LED!" 程序,并将其烧录到你的开发板上,亲眼看到 Rust 代码在硬件上运行的效果!