Rust 作為一門年輕的語言,聚焦與安全、并發、高性能等特點,號稱能替代 C/C++,那么究竟有多少優點能值得我們來切換呢?本文將告訴你為什么 Rust 適合嵌入式開發。
工具鏈
- 輕松搭建各種不同類型的芯片編譯環境,使用 cargo、rustup 等命令快速搭建新的環境。
- 統一的編譯、調試工具、下載工具, 如
cargo build
,cargo falsh
,probe-rs
等 - 快速生成代碼文檔,
cargo doc
直接生成網頁文檔,能讓新手快速了解整個項目的模塊、接口框架。
- 內置代碼格式化工具
fmt
,輕松就各個代碼文件統一格式,標準規范,團隊作戰無需在吐槽隊友的代碼風格,cargo fmt
后格式都會統一好,新手無需學習新公司的編碼格式規范,公司也無需過多培訓編碼格式規范。
庫的集成
- 移植優勢
可移植性移植是嵌入式開發的一個大問題,每個工作可能或多或少會考慮一些模塊的可移植性,想進來在不同框架不同平臺上能夠共有這些模塊,避免重復造輪子。Rust 則提供了高效的方法來保證庫能夠輕松移植,避免庫接口域業務接口雜糅到一起。trait 等特性讓 Rust 的庫能在不同的 CPU 如 ARM 或 RISV-V 甚至操作系統上方便得使用,無需過多關注庫的文件數量、無需手動添加庫的每個文件,僅僅只需在toml
文件中添加庫的名字、版本、開啟所需的featues
即可。在 Rust 中你可以輕松得將各種 IIC 的傳感器庫添加到自己的工程,很少花時間在適配上。
[dependencies]
panic-halt = "0.2.0"
ufmt = "0.2.0"
nb = "1.1.0"
embedded-hal = "1.0"
pwm-pca9685 = "1.0.0"
infrared = "0.14.1"
embedded-storage = "0.2"
[dependencies.embedded-hal-v0]
version = "0.2.3"
package = "embedded-hal"
2.Rust 官方發布了許多標準的庫,基于這些庫能簡化開發、指導用戶完成統一的適配接口。Rust 社區也非常活躍,發布了大量的開源庫。
As part of the Rust open source project, support for embedded systems is driven by a best-in-class open source community with support from commercial partners.
調試
Rust 生成的固件能使用 openocd 來輕松 gdb 調試,與 C/C++ 完全一樣,單步、斷電、查看等操作都支持。無需擔心調試障礙。
(gdb) break main
Breakpoint 1 at 0x8000d18: file examples/hello.rs, line 15.
(gdb) continue
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 1, main () at examples/hello.rs:15
15 let mut stdout = hio::hstdout().unwrap();
語言優勢
1.內存安全優勢。
C/C++嵌入式工程師肯定知道,經常在編碼完成后,燒錄程序到芯片測試運行,經常會出現莫名的內存泄露、異常退出甚至死機的現象,這種內存問題有時非常難怕查,也許編碼十分鐘調試兩小時。當然目前也有一些先進的工具用來輔助調試,如 ASan,Valgrind、Memcheck 等工具,但這些工具本身就需要復雜的調試手段,需要仔細查看日志才能得出結果,但是對于某些資源受限嵌入式設備,很難使用這些工具來輔助排查。對于新手來說學習這些工具的使用就讓人頭疼。而 Rust 天生保證內存安全,沒有豐富的 Rust 經驗很難寫出能讓內存異常的代碼,是的你沒聽錯,Rust 對于新手保護特別友好,需要有經驗的人才能故意寫出不安全的代碼。Rust 的生命周期的約束使得實現內存安全而且零成本,也就是無需在時間和空間上浪費資源。
2.語法優勢:語法中新的枚舉,閉包、異步、流控、變量生命周期控制、安全宏等,基于這些基礎語法能最方便、便捷的表達問題的邏輯,無需使用太多的技巧。讓編程更加簡潔和優雅。
// 定義一個數組,并初始化數組內容全部為 0x5f
let mut uart_rx_buf:[u8; 100] = [0x5f; 100];
// 定義一個閉包
let f_max_closue = |a: u32, b: u32| -> bool {
a > b
}
let max = f_max_closue(1, 2);
// 異步函數
let rst = rx.read().wait;
Rust 作為強類型的語言,但是在使用時無需過多指定變量類型,Rust 編譯器會自動推導變量的類型,并基于生命周期的約束可以重復使用變量名,原理上保證使用這些語法就像 C 語言一樣安全,但是用起來像 Python 一樣方便。
3.線程安全,無畏并發
Rust makes it impossible to accidentally share state between threads. Use any concurrency approach you like and you’ll still get Rust’s strong guarantees
一般來說 Rust 與其他語言也會面臨同一樣的并發問題。對于嵌入式軟件環境,包括:
- 多線程
- 多核處理器
- 中斷處理
對于以上三種常見并發的問題,Rust 也提供了高效的解決方案,如定義原子類型、臨界類型、互斥體防止被中斷影響。同時也在編譯期間檢測多線程引起死鎖風險,讓風險扼殺在編譯期間。目前已經有優秀的embassy
,rtic
等框架提供異步操作系統。
4.智能的編譯提示,對于編譯時的錯誤,給出詳細的原因,對于有風險的代碼段給出解決的意見,從代碼編寫階段提高代碼質量,無需在調試時去發現再優化,讓程序員花給多的時間來考慮代碼邏輯,業務邏輯,避免低效的調試過程。
5.輕松銜接 C/C++的代碼,零成本接口綁定
Integrate Rust into your existing C codebase or leverage an existing SDK to write a Rust application.
如果目前你的項目無法使用 Rust 來完成所有的模塊,你也可以使用 FFF 機制來輕松綁定原項目的 C/C++接口,能夠輕松與 C/C++互相操作。可以使用bindgen
命令來輕松構建外部接口,也可在build.rs
中編譯 C/C++ 文件,也能 C/C++ 調用庫文件如*.a
。輕松集成。
/* File: cool_bindings.rs */
#[repr(C)]
pub struct CoolStruct {
pub x: cty::c_int,
pub y: cty::c_int,
}
pub extern "C" fn cool_function(
i: cty::c_int,
c: cty::c_char,
cs: *mut CoolStruct
);
pub extern "C" fn cool_function( ... );
extern crate cc;
fn main() {
cc::Build::new()
.file("foo.c")
.compile("libfoo.a");
}
6.底層控制能力
Rust 也提供了接口能盡可能安全訪問地訪問底層接口,如 PAC 包用來抽象微控制器的外設寄存器的訪問,能編譯成高效的二進制代碼且接口容易使用,用戶無需太多關注寄存器各域的位置,只需聚焦于芯片手冊來操作各域的值,不會出現移位錯或寫錯的低級錯誤。
#![no_std]
#![no_main]
extern crate panic_halt; // panic handler
use cortex_m_rt::entry;
use tm4c123x;
#[entry]
pub fn init() -> (Delay, Leds) {
let cp = cortex_m::Peripherals::take().unwrap();
let p = tm4c123x::Peripherals::take().unwrap();
let pwm = p.PWM0;
pwm.ctl.write(|w| w.globalsync0().clear_bit());
// Mode = 1 => Count up/down mode
pwm._2_ctl.write(|w| w.enable().set_bit().mode().set_bit());
pwm._2_gena.write(|w| w.actcmpau().zero().actcmpad().one());
// 528 cycles (264 up and down) = 4 loops per video line (2112 cycles)
pwm._2_load.write(|w| unsafe { w.load().bits(263) });
pwm._2_cmpa.write(|w| unsafe { w.compa().bits(64) });
pwm.enable.write(|w| w.pwm4en().set_bit());
}