前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >🦀 新手必看!小小白也能用Rust点亮LED,入门嵌入式开发(下)

🦀 新手必看!小小白也能用Rust点亮LED,入门嵌入式开发(下)

原创
作者头像
程序员吾真本
修改2024-12-07 18:30:04
修改2024-12-07 18:30:04
1380
举报

讲动人的故事,写懂人的代码

🦀 新手必看!小小白也能用Rust点亮LED,入门嵌入式开发(上)中,我们探讨了为什么对编程新手而言,Rust的吸引力不在于内存安全,以及为何用Rust点亮LED比写Hello World更有趣。我们还学习了如何通过Rust代码点亮第一个LED灯,开启了Rust嵌入式开发之旅。

本篇将为你介绍这个项目源代码的四个关键类型,帮助你理解这些源代码的结构,了解各类源文件的特点,以及探索作为嵌入式开发的main.rs入口文件有何特别之处。

1.3.4 解读代码

在详细解释代码之前,让我们先来看看这个项目的文件结构(以下用//标注的是我的解释性注释):

代码语言:bash
复制
. // 项目根目录
├── .cargo
│   └── config.toml // 定义cargo工具行为和项目构建环境,
                    // 配置交叉编译目标平台、烧录工具、编译器和链接器参数
├── .git // 存储版本控制信息的文件夹
(其他行略)
├── .gitignore // 指定不需要纳入版本控制的文件
├── Cargo.lock // 锁定所有依赖的具体版本,确保构建的可重现性,自动生成,无须手动更改
├── Cargo.toml // Rust项目的主配置文件,包含依赖包及其版本号
├── Embed.toml // 嵌入式项目特定的配置文件,包含目标微控制器芯片型号和调试设置
├── README.md // 项目说明文档
└── src
    └── main.rs // 项目的主要源代码入口文件

12 directories, 12 files

在这个项目文件结构中,我们将重点关注以下四类文件:

  • 通用Rust项目包管理文件:Cargo.toml和Cargo.lock
  • Cargo工具行为和项目构建环境配置文件:.cargo/config.toml
  • 嵌入式开发特定配置文件:Embed.toml
  • Rust源代码入口文件:src/main.rs

接下来我们将逐一解读这些文件(通过添加注释的方式)。

通用Rust项目包管理文件:Cargo.toml

如代码清单1-2所示:

代码清单1-2 ch01/lu1l/Cargo.toml

代码语言:bash
复制
[package]
# 项目名称
name = "lu1l"
# 项目版本号
version = "0.1.0"
# Rust版本要求(2021版)
edition = "2021"

# 依赖包及其版本号
[dependencies]
# Cortex-M启动运行时支持
cortex-m-rt = "0.7.3"
# 提供panic处理机制,程序崩溃时停止运行
panic-halt = "0.2.0"
# 通过RTT提供panic信息输出
panic-rtt-target = "0.1.3"
# RTT(实时传输)调试工具
rtt-target = "0.5.0"
# BBC micro:bit v2开发板支持包
microbit-v2 = "0.15.0"
# 嵌入式硬件抽象层接口
embedded-hal = "1.0.0"

# 有关Cortex-M处理器核心的配置
[dependencies.cortex-m]
# Cortex-M处理器核心功能支持
version = "0.7.7"
# 启用内联汇编和单核心关键区段功能
features = ["inline-asm", "critical-section-single-core"]

Cargo.toml是Rust项目的核心配置文件,它记录了项目的基本信息,如项目名称、版本号和所需的依赖包。

它的设计巧妙而优雅。它使用易读的TOML格式,采用清晰的结构将项目信息和依赖项分开管理。更重要的是,它提供了极大的灵活性,让开发者可以为不同平台配置依赖,并自由选择所需功能。

Cargo.toml的便利性令人称赞。它不仅支持精确的版本控制和便捷的依赖管理,还能适配多种开发平台。由于在Rust生态中被广泛采用,新手能快速上手。它还支持多样化的依赖源,包括本地文件和Git仓库。

当然,它也存在一些小挑战。新手可能需要时间来熟悉版本号的表示方式。在大型项目中,处理依赖冲突可能较为复杂。此外,过多的依赖可能影响编译速度和程序大小。

但Cargo.toml的通用性令人印象深刻。它在普通应用开发和嵌入式开发中都表现出色,让开源库的使用者能按需选择功能,也为新手学习项目构建提供了良好的起点。

以本项目为例,配置文件指定了项目名称"lu1l"和Rust 2021版本,并引入了嵌入式开发所需的依赖包,如cortex-m-rt和embedded-hal。它还特别为BBC micro:bit v2开发板提供支持,并配置了多种崩溃处理机制。

Cargo.toml简化了项目管理流程,显著提升了开发效率,是个人开发和团队协作中的得力助手。

通用Rust项目包管理文件:Cargo.lock

如代码清单1-3所示:

代码清单1-3 ch01/lu1l/Cargo.lock

代码语言:bash
复制
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
(其他行略)

Cargo.lock 文件是 Rust 项目中的特殊存在,由 Cargo 工具自动生成和维护,用于记录项目所有依赖包的具体版本信息和元数据。你无需手动修改它,Cargo 会处理好一切。

这个文件有两个显著特征:它会精确记录项目中所有直接和间接依赖的具体版本号,确保构建环境的一致性;同时它是完全自动化的,Cargo 会根据 Cargo.toml 的内容自动更新它。为避免问题,文件开头还特别注明了不建议手动修改。

Cargo.lock 的好处显而易见。在团队开发中,它确保所有成员使用相同版本的依赖,避免"我这能运行,你那却不行"的尴尬。即使很久后重新构建项目,它也能保证生成的程序与之前完全一致。对团队协作而言,这种依赖版本的统一管理大大减少了冲突可能。

当然,它也有一些局限。当依赖项发布修复版本时,你需要手动运行 cargo update 来更新。在多人开发时,如果大家都更新了依赖,可能导致文件合并冲突。对新手而言,理解 Cargo.lock 和 Cargo.toml 的关系需要一些时间。

那么,什么时候该使用 Cargo.lock 呢?如果你在开发应用程序,尤其是需要团队协作或持续集成的项目,就应该将它纳入版本控制。但开发代码库时通常不建议提交 Cargo.lock,因为库的依赖版本应由最终应用决定。对需要长期维护的项目,Cargo.lock 能确保项目稳定性,即使外部依赖发生变化。

Cargo.lock 是 Rust 生态系统中不可或缺的工具。它通过确保依赖管理的一致性,为项目的可靠性和可重现性提供了有力保障,尤其在团队协作和长期维护场景中更显价值。

Cargo工具行为和项目构建环境配置文件:.cargo/config.toml

如代码清单1-4所示:

代码清单1-4 ch01/lu1l/.cargo/config.toml

代码语言:bash
复制
[build]
# 设置目标平台为ARM Cortex-M4F处理器核心(thumb v7em架构,带硬件浮点运算)
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
# 使用probe-rs工具运行程序,指定微控制器芯片型号为nRF52833
runner = "probe-rs run --chip nRF52833_xxAA"

# 设置rustc编译器参数:
rustflags = [
   # 使用rust-lld作为链接器
   "-C", "linker=rust-lld",
   # 指定链接脚本为link.x
   "-C", "link-arg=-Tlink.x",
]

.cargo/config.toml 文件中部分配置我们之前已经详细讨论过。它是 Rust 项目的构建环境配置文件,用于控制 Cargo 工具的行为。通过这个文件,开发者可以在项目或全局层面自定义 Cargo,灵活地配置构建选项、运行器和编译器标志。

这个配置文件设计精巧,采用分层配置方式。你既可以在用户主目录下设置全局配置,也可以为特定项目设置独立配置。它支持为不同目标平台定制特殊行为,比如为thumbv7em-none-eabihf架构设置专属配置。它的灵活性使其能适配几乎所有构建环境。

这个配置文件的优势显著。它对各类目标平台的支持完善,在嵌入式开发和多架构构建方面表现出色。通过配置runner,它能实现程序构建和运行的自动化,这对嵌入式开发尤其重要。它采用集中化配置方式,简化了项目维护。最重要的是,它能实现项目间的完全隔离,极大地促进了团队协作。

不过它也有一些挑战。新手可能需要时间理解rustflags或runner等配置选项。当配置出现问题时,排查可能比较困难,特别是在跨平台或嵌入式开发中。此外,使用probe-rs等外部工具时需要额外安装和学习。

这个配置文件适用于多种场景。在嵌入式开发中,它可以为Cortex-M4处理器配置专门的编译选项。在多架构项目中,它能为不同目标平台设置独特配置。在团队开发中,它可以统一构建环境。它还支持特定工具链集成,如通过probe-rs进行硬件调试。

以本项目为例,配置文件将默认目标平台设为thumbv7em-none-eabihf(专为Cortex-M4F架构设计),并配置probe-rs作为运行器,用于向nRF52833芯片烧录程序。通过设置编译器标志,它使用rust-lld作为链接器,并用link.x脚本控制程序内存布局。

.cargo/config.toml是一个功能强大且灵活的配置文件,特别适合需要精确控制构建流程的场景。它能显著提升开发效率,但要充分发挥其潜力,开发者需要深入了解相关配置选项。

嵌入式开发特定配置文件:Embed.toml

如代码清单1-5所示:

代码清单1-5 ch01/lu1l/Embed.toml

代码语言:bash
复制
[default.general]
# 指定目标芯片型号为nRF52833
chip = "nrf52833_xxAA"

[default.reset]
# 复位后保持芯片暂停状态
halt_afterwards = true

[default.rtt]
# 禁用RTT(实时传输)调试功能
enabled = false

[default.gdb]
# 启用GDB调试功能
enabled = true

Embed.toml是一个专为嵌入式开发设计的重要配置文件,用于管理芯片信息和调试设置。通过它,开发者可以方便地控制probe-rs等调试工具的行为。

该配置文件具有三个主要特点:专门针对嵌入式开发,包含完整的芯片和调试配置选项;采用简洁的TOML格式;配置灵活,可随时调整各模块设置。

Embed.toml的优势显著:它将所有设置集中管理,提高开发效率;支持配置复用,便于团队协作;可精确控制调试功能;结构清晰,易于理解和共享。

不过它也存在一些限制:依赖特定工具链,需要一定学习时间,复杂项目中可能需要配合其他配置文件使用。

这个配置文件最适合以下场景:涉及特定硬件平台调试和烧录的嵌入式项目、需要统一调试环境的团队协作,以及有特殊调试需求的项目。它尤其适用于nRF52833等特定芯片的开发。

以本项目为例,我们在配置文件中指定了nRF52833_xxAA作为目标芯片,设置了复位后暂停调试,关闭RTT调试功能,并启用GDB调试。这些配置让开发过程更可控、高效。

Embed.toml是嵌入式开发中的重要工具。它简化了调试配置流程,提升了开发效率。虽然有一定的学习门槛,但对经常进行嵌入式开发的团队来说,这是一个值得投入的工具。

Rust源代码入口文件:src/main.rs

如代码清单1-6所示:

代码清单1-6 ch01/lu1l/src/main.rs

代码语言:rust
复制
// 禁用不安全代码
#![deny(unsafe_code)]
// 声明这是一个独立程序,不使用标准入口点
#![no_main]
// 不使用标准库,这是嵌入式系统常见做法
#![no_std]

// 导入必要的嵌入式开发库
// 指定程序起点
use cortex_m_rt::entry;
// 操作硬件接口
use embedded_hal::digital::OutputPin;
// 控制开发板
use microbit::board::Board;
// 导入 panic 运行时错误处理程序
use panic_halt as _;

// 程序入口点
#[entry]
fn main() -> ! {  // 返回 ! 表示这是一个不返回的函数
    // 获取 microbit 板的控制权
    let mut board = Board::take().unwrap();
    
    // 点亮LED点阵的第4行第4列的LED,
    // 即在设置第4列为低电平的同时,设置第4行为高电平
    board.display_pins.col4.set_low().unwrap();
    board.display_pins.row4.set_high().unwrap();
    
    // 无限循环保持程序运行
    loop {}
}

最后让我来带你一步步了解这段嵌入式程序代码。作为小小白,你可能觉得嵌入式开发很神秘,但它其实有着独特的魅力。

首先,代码中有一个重要的声明:#![deny(unsafe_code)]。这行代码告诉编译器:"我们只写安全的代码"。虽然嵌入式开发常常需要直接操作硬件,但Rust提供了安全的封装,让我们无需直接处理那些危险的底层操作。

接着,你会发现这个程序与普通电脑程序不同。它使用了#![no_main]标记,因为嵌入式设备没有操作系统,因此无法使用标准的 main() 函数入口,程序需要直接与硬件交互。可以想象,这就像是程序直接成为了设备的"大脑"。嵌入式开发需要指定自己的程序入口点,后面会使用 cortex_m_rt::entry 宏指定 main 为程序的入口。

由于嵌入式设备资源有限,我们不能使用标准库中那些耗资源的功能。因此用#![no_std]告诉编译器:我们需要更精简的编程方式。这就像在小房子里生活,必须高效利用每一寸空间。

为了控制硬件,我们引入了几个关键的库。它们像是一座桥梁,让我们能用简单的方式控制复杂的硬件。我们用cortex_m_rt::entry指定程序起点,用embedded_hal操作硬件接口,用microbit::board::Board控制开发板,用panic_halt处理软件在运行时的错误。

程序的核心是一个永不结束的main函数。这很有趣,因为嵌入式程序需要持续运行。就像家里的智能设备,它们始终待命,随时响应操作。

在获取硬件控制权时,我们采用了特殊设计:Board::take()。这确保同一时间只有一个程序部分能控制硬件,就像给设备上了一把锁,防止意外发生。

最后,我们用几行简单的代码就点亮了LED灯。通过设置引脚的高低电平,我们能控制LED的亮灭。这就是嵌入式编程的魅力:用软件直接控制硬件,让静态的电路板变得生动起来。

这段嵌入式程序有四个关键特点:运行在资源受限的环境中,直接与硬件交互,精确控制每个引脚,并持续运行。虽然看起来复杂,但有了Rust这样的现代工具,初学者也能逐步掌握这项技术。

🧠基于上文对 Rust 项目包管理文件 Cargo.toml 和 Cargo.lock 的介绍,以及你在本章使用 cargo 的实践经验,Rust 的包管理系统中具体哪个优秀的用户体验让你印象深刻?

如何把点亮LED灯的Rust代码改为让LED灯闪烁?怎样让VS Code自动显示let语句推断的变量类型?如何在VS Code中安装免费的AI编程助手Codeium?敬请在本文底部点击本合集“小小白学Rust”,阅读下一篇“🦀掌握Rust编程神器,让LED灯闪烁(上)”。


如果喜欢这篇文章,别忘了给文章点个“赞”,好鼓励小吾继续写哦~😃

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.3.4 解读代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档