使用 Rust 进行嵌入式开发

Rust 的高性能、可靠性和生产力使其非常适合嵌入式系统。
77 位读者喜欢这篇文章。
Ferris the crab under the sea, unofficial logo for Rust programming language

Opensource.com

在过去的几年中,Rust 在程序员中获得了热情的追随者。技术潮流来来去去,因此很难区分仅仅因为某项技术是新的而产生的兴奋,还是因其优点而产生的兴奋,但是 RT-Thread 社区开发者 Liu Kang 认为 Rust 是一种设计精良的语言。Kang 说,Rust 旨在帮助开发人员构建可靠且高效的软件,并且它是从头开始为此目的而设计的。您会听到关于 Rust 的一些关键特性,在本文中,Kang 演示了为什么这些特性中的许多特性也恰恰是 Rust 非常适合嵌入式系统的原因。以下是一些例子

  • 高性能:速度快,内存利用率高
  • 可靠性:内存错误可以在编译期间消除
  • 生产力:出色的文档,友好的编译器和有用的错误消息,以及一流的工具。它具有集成的包管理器和构建工具,智能多编辑器支持(具有自动完成和类型检查),自动格式化程序等等。

为什么使用 Rust 进行嵌入式开发?

Rust 旨在保证安全性和高性能。嵌入式软件可能会出现问题,主要是由于内存。Rust 在某种程度上是一种面向编译器的语言,因此您可以确保在编译时安全地使用内存。以下是使用 Rust 在嵌入式设备上进行开发的一些好处

  • 强大的静态分析
  • 灵活的内存
  • 无畏的并发性
  • 互操作性
  • 可移植性
  • 社区驱动

在本文中,我使用开源 RT-Thread 操作系统 来演示如何使用 Rust 进行嵌入式开发。

如何在 C 中调用 Rust

在 C 代码中调用 Rust 代码时,您必须将 Rust 源代码打包为静态库文件。当 C 代码编译时,将其链接进来。

使用 Rust 创建静态库

此过程包含两个步骤。

1. 使用 cargo init --lib rust_to_c 在 Clion 中构建一个 lib 库。将以下代码添加到 lib.rs 中。以下函数评估类型为 i32 的两个值的总和并返回结果

#![no_std]
use core::panic::PanicInfo;

#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
    a + b
}

#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

2. 将以下代码添加到您的 Cargo.toml 文件中,以告诉 Rustc 要生成哪种类型的库

[lib]
name = "sum"
crate-type = ["staticlib"]
path = "src/lib.rs"

交叉编译

您可以为您的目标进行交叉编译。假设您的嵌入式系统是基于 Arm 的,则步骤很简单

$ rustup target add armv7a-none-eabi

2. 生成静态库文件

$ cargo build --target=armv7a-none-eabi --release --verbose
Fresh rust_to_c v0.1.0
Finished release [optimized] target(s) in 0.01s

生成头文件

您也需要头文件。

1. 安装 cbindgencbindgen 工具从 Rust 库生成 C 或 C++11 头文件

$ cargo install --force cbindgen

2. 在您的项目文件夹下创建一个新的 cbindgen.toml 文件。

3. 生成头文件

$ cbindgen --config cbindgen.toml --crate rust_to_c --output sum.h

调用 Rust 库文件

现在您可以调用您的 Rust 库了。

1. 将生成的 sum.hsum.a 文件放入 rt-thread/bsp/qemu-vexpress-a9/applications 目录。

2. 修改 SConscript 文件并添加一个静态库

   from building import *
   
   cwd     = GetCurrentDir()
   src     = Glob('*.c') + Glob('*.cpp')
   CPPPATH = [cwd]
   
   LIBS = ["libsum.a"]
   LIBPATH = [GetCurrentDir()]
   
   group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH, LIBS = LIBS, LIBPATH = LIBPATH)
   
   Return('group')

3. 在 main 函数中调用 sum 函数,获取返回值,并 printf 该值。

   #include <stdint.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <rtthread.h>
   #include "sum.h"
   
   int main(void)
   {
       int32_t tmp;
   
       tmp = sum(1, 2);
       printf("call rust sum(1, 2) = %d\n", tmp);
   
       return 0;
   }

4. 在 RT-Thread Env 环境中,使用 scons 编译项目并运行

$ scons -j6
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
[...]
scons: done building targets.

$ qemu.sh
 \ | /
- RT -     Thread Operating System
 / | \     4.0.4 build Jul 28 2021
2006 - 2021 Copyright by rt-thread team
lwIP-2.1.2 initialized!
[...]
call rust sum(1, 2) = 3

加、减、乘、除

您可以使用 Rust 实现一些复杂的数学运算。在 lib.rs 文件中,使用 Rust 语言实现加、减、乘、除

#![no_std]
use core::panic::PanicInfo;

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

#[no_mangle]
pub extern "C" fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32) -> i32 {
    a / b
}

#[panic_handler]
fn panic(_info:&PanicInfo) -> !{
    loop{}
}

构建您的库文件和头文件,并将它们放在应用程序目录中。使用 scons 进行编译。如果在链接期间出现错误,请在官方 Github 页面 上查找解决方案。

修改 rtconfig.py 文件,并添加链接参数 --allow-multiple-definition

       DEVICE = ' -march=armv7-a -marm -msoft-float'
       CFLAGS = DEVICE + ' -Wall'
       AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -D__ASSEMBLY__ -I.'
       LINK_SCRIPT = 'link.lds'
       LFLAGS = DEVICE + ' -nostartfiles -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,system_vectors,--allow-multiple-definition'+\
                         ' -T %s' % LINK_SCRIPT
   
       CPATH = ''
       LPATH = ''

编译并运行 QEMU 以查看您的工作。

在 Rust 中调用 C

Rust 可以在 C 代码中调用,但是在您的 Rust 代码中调用 C 代码呢?以下是在 Rust 代码中调用 rt_kprintf C 函数的示例。

首先,修改 lib.rs 文件

    // The imported rt-thread functions list
    extern "C" {
        pub fn rt_kprintf(format: *const u8, ...);
    }
    
    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 {
        unsafe {
            rt_kprintf(b"this is from rust\n" as *const u8);
        }
        a + b
    }

接下来,生成库文件

$ cargo build --target=armv7a-none-eabi --release --verbose
Compiling rust_to_c v0.1.0
Running `rustc --crate-name sum --edition=2018 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type staticlib --emit=dep-info,link -C opt-level=3 -C embed-bitcode=no -C metadata=a
Finished release [optimized] target(s) in 0.11s

现在,要运行代码,请将 Rust 生成的库文件复制到应用程序目录并重新构建

$ scons -j6 scons: Reading SConscript files ... scons: done reading SConscript files. [...]
scons: Building targets ... scons: done building targets.

再次运行 QEMU 以在您的嵌入式镜像中查看结果。

您可以兼得

使用 Rust 进行嵌入式开发使您拥有 Rust 的所有功能,而无需牺牲灵活性或稳定性。立即在您的嵌入式系统上尝试 Rust。有关嵌入式 Rust 过程(以及 RT-Thread 本身)的更多信息,请查看 RT-Thread 项目的 YouTube 频道。请记住,嵌入式也可以是开源的。


特别感谢 Liu Kang 提供本文,并感谢他为使每个人都能轻松进行嵌入式编程所做的不懈努力!

接下来阅读
Avatar
我喜欢我的隐私。

评论已关闭。

© . All rights reserved.