Rust 系统编程:8.3 裸金属编程
裸金属编程(Bare Metal Programming)是指在没有任何操作系统或运行时环境的情况下,直接在硬件上运行程序。这种编程方式通常用于嵌入式系统、实时操作系统(RTOS)和低级硬件控制。Rust 作为一种系统编程语言,提供了强大的工具和特性,使其成为裸金属编程的理想选择。本文将深入探讨 Rust 中的裸金属编程,涵盖基本概念、开发步骤、内存管理、中断处理、以及实际示例。
8.3.1 裸金属编程概述
8.3.1.1 什么是裸金属编程?
裸金属编程是指直接在硬件上运行程序,而不依赖于操作系统或运行时环境。这种编程方式通常用于以下场景:
- 嵌入式系统:如微控制器(MCU)和传感器。
- 实时操作系统(RTOS):需要严格的时间控制和资源管理。
- 低级硬件控制:如设备驱动和固件开发。
8.3.1.2 Rust 在裸金属编程中的优势
- 内存安全:Rust 的所有权系统避免了常见的内存错误(如空指针、缓冲区溢出)。
- 高性能:Rust 生成的代码性能接近 C/C++。
- 零成本抽象:Rust 提供了高级抽象,而不会引入运行时开销。
8.3.2 裸金属编程开发步骤
8.3.2.1 选择目标硬件
裸金属编程的第一步是选择目标硬件。常见的硬件平台包括:
- STM32 系列:如 STM32F3、STM32F4。
- Raspberry Pi Pico:基于 RP2040 微控制器。
- ESP32:集成了 Wi-Fi 和蓝牙功能。
本文将以 STM32F3DISCOVERY 开发板为例。
8.3.2.2 配置开发环境
8.3.2.2.1 安装 Rust 工具链
首先,安装 Rust 工具链:
1
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
然后,添加嵌入式开发所需的工具链和目标:
1
2
3
|
rustup target add thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils
|
8.3.2.2.2 安装调试工具
安装 OpenOCD(用于调试和烧录):
1
|
sudo apt install openocd
|
8.3.2.3 创建裸金属项目
使用 cargo
创建一个新的裸金属项目:
1
2
|
cargo new --bin bare-metal
cd bare-metal
|
在 Cargo.toml
中添加依赖:
1
2
3
4
5
6
7
8
|
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
[dependencies.stm32f3xx-hal]
version = "0.8"
features = ["stm32f303xc"]
|
8.3.2.4 编写裸金属程序
在 src/main.rs
中编写裸金属程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#![no_std]
#![no_main]
use panic_halt as _;
use cortex_m_rt::entry;
use stm32f3xx_hal::{prelude::*, stm32};
#[entry]
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
let mut led = gpioe
.pe9
.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);
loop {
led.toggle().unwrap();
cortex_m::asm::delay(8_000_000); // 延迟约 1 秒
}
}
|
代码说明
#![no_std]
:禁用标准库,使用核心库(core)。
#![no_main]
:禁用标准 main
函数,使用 #[entry]
宏定义入口点。
panic_halt
:定义 panic 处理程序。
stm32f3xx_hal
:STM32F3 系列的硬件抽象层(HAL)。
gpioe.pe9
:配置 PE9 引脚为推挽输出,用于控制 LED。
8.3.2.5 编译和烧录程序
8.3.2.5.1 编译程序
使用 cargo
编译程序:
8.3.2.5.2 烧录程序
使用 OpenOCD 和 GDB 烧录程序:
-
启动 OpenOCD:
1
|
openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg
|
-
在另一个终端中启动 GDB:
1
|
arm-none-eabi-gdb -q target/thumbv7em-none-eabihf/release/bare-metal
|
-
在 GDB 中连接 OpenOCD 并烧录程序:
1
2
3
4
|
target remote :3333
load
monitor reset halt
continue
|
8.3.2.6 调试程序
使用 GDB 进行调试:
-
设置断点:
-
运行程序:
-
查看变量和寄存器:
1
2
|
print led
info registers
|
8.3.3 内存管理
8.3.3.1 内存布局
在裸金属编程中,程序需要直接管理内存。Rust 提供了 cortex-m-rt
库,用于定义内存布局和初始化堆栈。
以下是一个简单的内存布局示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
// 程序入口
loop {}
}
#[link_section = ".vector_table.interrupts"]
#[no_mangle]
pub static __INTERRUPTS: [unsafe extern "C" fn(); 240] = [{
extern "C" {
fn DefaultHandler();
}
DefaultHandler
}; 240];
|
代码说明
#[link_section = ".vector_table.interrupts"]
:将中断向量表放置在指定的内存段。
__INTERRUPTS
:定义中断向量表。
8.3.3.2 堆栈管理
在裸金属编程中,堆栈的管理非常重要。Rust 提供了 cortex-m-rt
库,用于初始化堆栈。
以下是一个简单的堆栈初始化示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
// 初始化堆栈
let mut stack = [0u8; 1024];
let stack_ptr = stack.as_mut_ptr();
unsafe {
cortex_m::register::msp::write(stack_ptr as u32);
}
loop {}
}
|
代码说明
cortex_m::register::msp::write
:设置主堆栈指针(MSP)。
8.3.4 中断处理
8.3.4.1 中断向量表
中断向量表是一个包含中断处理函数指针的数组。每个中断对应一个处理函数。
以下是一个简单的中断向量表示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn DefaultHandler() {
loop {}
}
#[no_mangle]
pub extern "C" fn SysTick() {
// 系统滴答定时器中断处理
}
|
代码说明
DefaultHandler
:默认中断处理函数。
SysTick
:系统滴答定时器中断处理函数。
8.3.4.2 启用和禁用中断
在裸金属编程中,需要手动启用和禁用中断。Rust 提供了 cortex_m::interrupt
模块,用于管理中断。
以下是一个简单的启用和禁用中断的示例:
1
2
3
4
5
6
7
8
|
use cortex_m::interrupt;
fn main() -> ! {
interrupt::enable();
interrupt::disable();
loop {}
}
|
代码说明
interrupt::enable
:启用中断。
interrupt::disable
:禁用中断。
8.3.5 实际应用示例
8.3.5.1 控制多个 LED
以下是一个控制多个 LED 的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
use stm32f3xx_hal::{prelude::*, stm32};
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
let mut leds = [
gpioe.pe9.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper),
gpioe.pe10.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper),
gpioe.pe11.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper),
gpioe.pe12.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper),
];
loop {
for led in leds.iter_mut() {
led.toggle().unwrap();
cortex_m::asm::delay(2_000_000); // 延迟约 0.25 秒
}
}
}
|
8.3.5.2 使用定时器
以下是一个使用定时器控制 LED 闪烁的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
use stm32f3xx_hal::{prelude::*, stm32, timer::Timer};
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
let mut led = gpioe
.pe9
.into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);
let mut timer = Timer::tim2(dp.TIM2, 1.hz(), rcc.apb1);
loop {
led.toggle().unwrap();
timer.start(1.hz());
timer.wait().unwrap();
}
}
|
8.3.6 总结
Rust 为裸金属编程提供了强大的工具和特性。本文详细介绍了裸金属编程的开发步骤、内存管理、中断处理,以及实际示例。通过 Rust 的内存安全性和高性能,开发者可以构建高效、可靠的裸金属程序。