使用 Rust 进行嵌入式开发
Rust 嵌入式

> 
> Rust 的高性能、高可靠性和高生产力使它适合于嵌入式系统。
> 
> 
> 


![](/data/attachment/album/202110/26/103848djcdhjk0d2z1hc52.jpg "Ferris the crab under the sea, unofficial logo for Rust programming language")


在过去的几年里，Rust 在程序员中获得了热情的追捧。技术潮流来来去去，所以很难将仅仅因为某项新技术而产生的兴奋与对某项技术的优点的兴奋区分开来，但我认为 Rust 是一种真正设计良好的语言。它的目标是帮助开发者建立可靠和高效的软件，而且它从一开始就是为这个目的设计的。你可能听过一些 Rust 的关键特性，在这篇文章中，我会证明这些特性正是 Rust 也恰好适合嵌入式系统的原因。比如：


* 高性能：它速度快，内存利用率高
* 可靠性：在编译过程中可以消除内存错误
* 生产力：很棒的文档，友好的编译器，有用的错误信息，以及一流的工具化。它有一个集成的包管理器和构建工具，智能的多编辑器支持自动补完和类型检查、自动格式化等等。


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


Rust 的设计是为了保证安全和高性能。嵌入式软件会出现的问题主要是内存的问题。从某种程度上说，Rust 是一种面向编译器的语言，所以你可以确保在编译时安全使用内存。以下是使用 Rust 在嵌入式设备上开发的一些好处：


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


在这篇文章中，我使用开源的 [RT-Thread 操作系统](https://github.com/RT-Thread/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

```

生成静态库文件：



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

```

#### 生成头文件


你也需要头文件：


1、安装 [cbindgen](https://github.com/eqrion/cbindgen)。`cbindgen` 工具会从 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.h` 和 `sum.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、在主函数中调用 `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](https://www.rt-thread.io/download.html?download=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 页面](https://github.com/rust-lang/compiler-builtins/issues/353) 中找到解决方案。


修改 `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 频道](https://www.youtube.com/channel/UCdDHtIfSYPq4002r27ffqP)。请记住，嵌入式也可以是开放的。




---


via: <https://opensource.com/article/21/10/rust-embedded-development>


作者：[Alan Smithee](https://opensource.com/users/alansmithee) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Over the past several years, Rust has gained a passionate following among programmers. Tech trends come and go, so it can be difficult to separate excitement just because something is new versus excitement over the merits of a technology, but [RT-Thread](https://opensource.com/article/21/7/rt-thread-smart) community developer Liu Kang believes that Rust is a truly well-designed language. Kang says that Rust aims to help developers build reliable and efficient software, and it was designed for that purpose from the ground up. There are key features you'll hear about Rust, and in this article, Kang demonstrates that many of these features are exactly why Rust also happens to be great for embedded systems. Here are some examples:

- High performance: It's fast, with high memory utilization
- Reliability: Memory errors can be eliminated during compilation
- Productivity: Great documentation, a friendly compiler with useful error messages, and top-notch tooling. There's an integrated package manager and build tool, smart multi-editor support with auto-completion and type inspections, an auto-formatter, and more.


## Why use Rust for embedded development?

Rust is designed to guarantee both security and high performance. Embedded software can have problems, mostly due to memory. Rust is, in a way, a compiler-oriented language, so you can be sure that you're using memory safely while compiling. Here are some of the benefits of using Rust to develop on embedded devices:

- Powerful static analysis
- Flexible memory
- Fearless concurrency
- Interoperability
- Portability
- Community-driven

In this article, I use the open source [RT-Thread operating system](https://github.com/RT-Thread/rt-thread) to demonstrate how to use Rust for embedded development.

## How to call Rust in C

When calling Rust code in C code, you must package the Rust source code as a static library file. When the C code compiles, link it in.

### Creating a static library with Rust

There are two steps in this process.

1. Use `cargo init --lib rust_to_c `

to build a lib library in Clion. Add the following code to the `lib.rs`

. The following function evaluates the sum of two values of type **i32** and returns the result:

```
#![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. Add the following code to your `Cargo.toml`

file to tell Rustc what type of library to generate:

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


### Cross-compilation

You can cross-compile for your target. Assuming your embedded system is Arm-based, the steps are simple:

`$ rustup target add armv7a-none-eabi`


2. Generate the static library file:

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


### Generate Header File

You need header files, too.

1. Install [cbindgen](https://github.com/eqrion/cbindgen). The `cbindgen`

tool generates a C or C++11 header file from the Rust library:

`$ cargo install --force cbindgen`


2. Create a new `cbindgen.toml`

file under your project folder.

3. Generate a header file:

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


### Call the Rust library file

Now you can make calls to your Rust libraries.

1. Put the generated `sum.h`

and `sum.a`

files into the `rt-thread/bsp/qemu-vexpress-a9/applications`

directory.

2. Modify the `SConscript`

file and add a static library:

```
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. Call the **sum** function in the main function, get the return value, and `printf`

the value.

```
#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. In the RT-Thread [Env](https://www.rt-thread.io/download.html?download=Env) environment, use `scons`

to compile the project and run:

```
$ 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
```


## Add, subtract, multiply, and divide

You can implement some complicated math in Rust. In the `lib.rs`

file, use the Rust language to implement add, subtract, multiply, and divide:

```
#![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{}
}
```


Build your library files and header files and place them in the application directory. Use `scons`

to compile. If errors appear during linking, find the solution on the official [Github page](https://github.com/rust-lang/compiler-builtins/issues/353).

Modify the `rtconfig.py`

file, and add the link parameter `--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 = ''
```


Compile and run QEMU to see your work.

## Call C in Rust

Rust can be called in C code, but what about calling C in your Rust code? The following is an example of calling the `rt_kprintf`

C function in Rust code.

First, modify the `lib.rs`

file:

```
// 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
}
```


Next, generate the library file:

```
$ 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
```


And now, to run the code, copy the library files generated by Rust into the application directory and rebuild:

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


Run QEMU again to see the results in your embedded image.

## You can have it all

Using Rust for your embedded development gives you all the features of Rust without the need to sacrifice flexibility or stability. Try Rust on your embedded system today. For more information about the process of embedded Rust (and about RT-Thread itself), check out the RT-Thread project's [YouTube channel](https://www.youtube.com/channel/UCdDHtIfSYPq4002r27ffqP). And remember, embedded can be open, too.

Special thanks to Liu Kang for providing this article, and for his tireless work on making embedded programming easy for everyone!

## Comments are closed.