在嵌入式系統開發中,Rust 語言的安全性與效能使其成為控制硬體的理想選擇。本文將引導您使用 Rust 和 rust_gpiozero 函式庫,在 Raspberry Pi 上進行 GPIO 控制,包含 LED 燈的開關和按鈕輸入的偵測,同時也涵蓋防抖動的處理技巧,避免因按鈕彈跳產生的誤判。此外,為了提高編譯速度,本文也將說明如何利用跨平台編譯的方式,在效能更佳的 x86 Linux PC 上編譯 Raspberry Pi 適用的執行檔,最後再將編譯好的檔案佈署至 Raspberry Pi 上執行,有效縮短開發週期。
樹莓派(Raspberry Pi)硬體與作業系統準備
在開始撰寫程式碼之前,瞭解你的樹莓派硬體和作業系統至關重要。現在花點時間做準備,日後將能避免許多麻煩。
認識你的樹莓派
本章節將使用樹莓派4型號B(Raspberry Pi 4 Model B),不過樹莓派3也同樣適用。樹莓派是一款微型電腦,具備一般電腦所需的所有元件:中央處理器(CPU)、記憶體、Wi-Fi、藍牙、HDMI輸出、USB等。
樹莓派4使用ARM架構的CPU,而大多數主流個人電腦則採用x86架構,搭載Intel或AMD的處理器。由於Rust是一種編譯成機器碼的語言,因此必須針對正確的CPU架構進行編譯,否則二進位制檔案將無法執行。
樹莓派具備多種周邊裝置,包括用於載入程式和作業系統的SD卡讀卡器、USB-C供電介面、micro-HDMI輸出介面等。此外,樹莓派還有兩排金屬針腳(GPIO),可用於與外部電子元件(如LED和按鈕)互動。
使用Raspberry Pi Imager安裝樹莓派作業系統
首先,你將學習如何在樹莓派上直接編譯和執行Rust程式。你需要在SD卡上安裝作業系統映像檔,讓樹莓派從該映像檔啟動。官方的樹莓派作業系統稱為Raspberry Pi OS,是一款根據Debian的作業系統,具備友善的桌面環境和許多預先安裝的軟體套件,如Firefox瀏覽器、文字編輯器、電腦等。
安裝Raspberry Pi OS最簡單的方法是使用Raspberry Pi Imager工具。以下是安裝步驟:
- 前往Raspberry Pi Imager下載頁面(https://www.raspberrypi.com/software/),依照指示安裝。
- 將至少8GB且格式化為FAT格式的SD卡插入電腦。
- 以root許可權啟動Raspberry Pi Imager,並依照螢幕指示將Raspberry Pi OS安裝到SD卡上。
- 將SD卡插入樹莓派。
- 將樹莓派連線鍵盤、滑鼠和HDMI顯示器。
- 使用5V/3A USB-C電源供應器(通常是手機充電器)為樹莓派供電,啟動裝置。
- 啟動後,你應該會看到類別似圖8-4的桌面環境。
內容解密:
安裝Raspberry Pi OS的過程涉及下載並使用Raspberry Pi Imager工具。這個工具提供了一個逐步的安裝精靈,引導你完成整個安裝過程。首先,你需要下載並安裝這個工具。接著,將SD卡插入電腦,並啟動Raspberry Pi Imager。根據螢幕上的指示,將Raspberry Pi OS安裝到SD卡上。完成後,將SD卡插入樹莓派,並連線必要的周邊裝置,如鍵盤、滑鼠和顯示器。最後,使用適當的電源供應器為樹莓派供電,裝置就會啟動並顯示桌面環境。
安裝Rust工具鏈
如同在Linux桌面上所做的那樣,你可以在Raspberry Pi OS上使用rustup安裝Rust編譯器和Cargo。開啟終端機並執行以下命令(來自Rust官方安裝頁面:https://www.rust-lang.org/tools/install):
curl https://sh.rustup.rs -sSf | sh
這會在樹莓派上安裝Rust工具鏈。rustup會偵測到ARM CPU,並建議安裝不同的目標架構(armv7-unknown-linux-gnueabihf)。你應該接受rustup的建議,安裝適用於ARM的工具鏈。安裝完成後,別忘了將Cargo資料夾加入PATH環境變數,以便使用Cargo命令。
內容解密:
這段程式碼使用curl命令下載並執行Rust安裝指令碼。rustup會根據你的CPU架構(在本例中為ARM)建議適當的目標架構。你需要接受這個建議,以確保Rust程式碼能夠正確編譯並在樹莓派上執行。
瞭解GPIO針腳
設定好樹莓派後,你將會看到佔據電路板一側的兩排金屬針腳。這些針腳被稱為GPIO(通用輸入/輸出)針腳,用於與外部世界通訊。當針腳作為輸出時,你可以透過軟體控制它輸出3.3伏特(標記為「3V3」)或0伏特。當針腳作為輸入時,它可以偵測針腳是否具有高電壓(3V3)或低電壓(0V)。
內容解密:
GPIO針腳是用於與外部電子元件互動的重要介面。你可以透過軟體控制GPIO針腳的輸出電壓,或者偵測其輸入電壓。這使得樹莓派能夠與各種外部裝置進行互動,例如LED燈和按鈕。
GPIO針腳型別
GPIO針腳有多種型別,包括:
- 5V:5伏特電源供應
- 3V3:3.3伏特電源供應
- GND:接地(恆定0V)
- 編號:對於GPIO針腳,編號是Broadcom SOC通道(BCM)編號。這是用於在範例程式碼中識別針腳的編號。
內容解密:
瞭解不同型別的GPIO針腳對於正確使用它們至關重要。你需要知道哪些針腳提供電源、哪些針腳接地,以及哪些針腳可用於輸入/輸出操作。此外,BCM編號是用於識別GPIO針腳的重要參考依據。
使用 Rust 控制 Raspberry Pi 的 GPIO
在物聯網和嵌入式系統開發中,Raspberry Pi 是一款非常流行的開發板。它提供了豐富的 GPIO(通用輸入/輸出)介面,可以用於連線各種外部裝置,如 LED、按鈕和感測器等。本文將介紹如何使用 Rust 語言控制 Raspberry Pi 的 GPIO 介面,以實作對 LED 的控制。
GPIO 簡介
Raspberry Pi 的 GPIO 介面是一組可以用於輸入或輸出訊號的引腳。這些引腳可以用於實作各種功能,如數字輸入/輸出、脈寬調製(PWM)、序列外設介面(SPI)、I2C 匯流排和序列通訊(UART)等。在本例中,我們將重點介紹如何使用 GPIO 引腳控制 LED。
使用 rust_gpiozero 函式庫
為了簡化 GPIO 操作,我們可以使用 rust_gpiozero 函式庫。該函式庫提供了對 GPIO 引腳的抽象,使得控制外部裝置變得更加容易。首先,您需要在 Raspberry Pi 上建立一個新的 Rust 專案,並將 rust_gpiozero 新增到專案的依賴中。
$ cargo new physical-computing
$ cd physical-computing
$ cargo add rust_gpiozero
控制 LED
在開始之前,您需要搭建一個簡單的電路,將 LED 連線到 Raspberry Pi 的 GPIO 引腳上。請確保在電路中新增一個電阻,以限制流經 LED 的電流,防止 LED 損壞。
電路連線
- 將 LED 的正極(通常是較長的引腳)連線到 GPIO 引腳 2。
- 將 LED 的負極連線到電阻的一端。
- 將電阻的另一端連線到 GND(地)。
Rust 程式碼
// src/main.rs
use rust_gpiozero::*;
use std::thread::sleep;
use std::time::Duration;
fn main() {
let led = LED::new(2);
loop {
println!("LED on");
led.on();
sleep(Duration::from_secs(1));
println!("LED off");
led.off();
sleep(Duration::from_secs(1));
}
}
內容解密:
- 初始化 LED:
let led = LED::new(2);這行程式碼建立了一個新的LED結構例項,並將其關聯到 GPIO 引腳 2。在初始化過程中,rust_gpiozero函式庫會自動將該引腳設定為輸出模式。 - 控制 LED 狀態:
led.on()和led.off()分別用於開啟和關閉 LED。當呼叫led.on()時,GPIO 引腳 2 被設定為高電平(3.3V),使得 LED 點亮。相反,當呼叫led.off()時,引腳被設定為低電平(0V),LED 熄滅。 - 迴圈控制:程式碼中使用了一個無限迴圈,使得 LED 每隔一秒鐘閃爍一次。
sleep(Duration::from_secs(1))用於在開啟和關閉 LED 之間引入延遲。
使用 Rust 控制 GPIO:從 LED 控制到按鈕輸入
本章將探討如何使用 Rust 語言控制 Raspberry Pi 的 GPIO(通用輸入輸出)介面,從基本的 LED 控制到按鈕輸入檢測,並介紹如何處理按鈕抖動問題。
LED 控制:基礎與進階
首先,我們來看看如何使用 Rust 控制 LED 燈的開關。透過 rust_gpiozero 函式庫,我們可以輕鬆地控制 GPIO 腳位。
簡單的 LED 開關
// src/main.rs
use rust_gpiozero::*;
use std::time::Duration;
use std::thread::sleep;
fn main() {
let mut led = LED::new(2);
loop {
println!("on");
led.on();
sleep(Duration::from_secs(1));
println!("off");
led.off();
sleep(Duration::from_secs(1));
}
}
使用 LED::blink() 函式
rust_gpiozero 提供了 LED::blink() 函式,讓我們可以更簡單地實作 LED 的閃爍效果。
// src/main.rs
use rust_gpiozero::*;
fn main() {
let mut led = LED::new(2);
led.blink(1.0, 1.0);
led.wait(); // 防止程式立即離開
}
內容解密:
LED::new(2)初始化一個新的 LED 結構,引數2表示使用的 GPIO 腳位。led.blink(1.0, 1.0)使 LED 每隔一秒閃爍一次,第一個1.0表示 LED 亮起的時間,第二個1.0表示 LED 熄滅的時間。led.wait()用於保持程式執行,防止其在led.blink()之後立即離開。
按鈕輸入檢測
接下來,我們將學習如何使用 GPIO 檢測物理按鈕的輸入。
按鈕電路設計
為了檢測按鈕的按下,我們需要組態 GPIO 腳位為輸入模式,並使用內部上拉或下拉電阻來穩定電壓。
// src/main.rs
use rust_gpiozero::*;
fn main() {
let mut led = LED::new(2);
let mut button = Button::new(4);
loop {
println!("wait for button");
button.wait_for_press(None);
println!("button pressed!");
led.toggle();
}
}
內容解密:
Button::new(4)初始化一個按鈕結構,使用 GPIO 4 號腳位,並預設啟用內部上拉電阻。button.wait_for_press(None)使程式阻塞,直到按鈕被按下。None表示無逾時限制,可以替換為Some(f32)來設定逾時時間(單位:秒)。led.toggle()在按鈕按下後切換 LED 的狀態。
處理按鈕抖動
在實際應用中,按鈕的機械結構可能會導致抖動,從而觸發多次按鈕事件。為瞭解決這個問題,我們可以實作按鈕去抖動。
// src/main.rs
use rust_gpiozero::*;
use std::time::{Duration, Instant};
fn main() {
let mut led = LED::new(2);
let mut button = Button::new(4);
let mut last_clicked = Instant::now();
loop {
button.wait_for_press(None);
if last_clicked.elapsed() < Duration::new(1, 0) {
continue;
}
led.toggle();
last_clicked = Instant::now();
}
}
內容解密:
- 使用
Instant::now()紀錄上一次按鈕被按下的時間。 - 在檢測到按鈕按下後,檢查距離上一次按下是否已經超過一秒。如果未超過,則忽略此次事件。
- 超過一秒才會切換 LED 狀態,並更新
last_clicked時間。
實作 Raspberry Pi 的實體運算與跨平台編譯
在前面的章節中,我們探討瞭如何在 Raspberry Pi 上使用 Rust 進行實體運算。本章節將探討如何使用 Rust 在 Raspberry Pi 上進行 GPIO 控制,並介紹跨平台編譯的概念。
8.2 防彈跳處理
當我們使用按鈕控制 LED 時,可能會遇到按鈕彈跳的問題。按鈕彈跳是指當按鈕被按下或釋放時,由於機械原因導致的多次觸發。為瞭解決這個問題,我們需要進行防彈跳處理。
let mut last_clicked = Instant::now();
loop {
button.wait_for_press().unwrap();
if last_clicked.elapsed() < Duration::new(1, 0) {
continue;
}
led.toggle();
last_clicked = Instant::now();
}
內容解密:
last_clicked變數用於記錄上一次按鈕被按下的時間。- 當
button.wait_for_press()傳回時,我們檢查last_clicked.elapsed(),即自上次按鈕被按下以來所經過的時間。 - 如果經過的時間少於一秒 (
Duration::new(1, 0)),則此次按下事件被視為彈跳並被忽略。 - 如果已經過了一秒,則切換 LED 的狀態,並更新
last_clicked的時間戳。
8.3 跨平台編譯到 Raspberry Pi
由於 Raspberry Pi 的 CPU 效能相對較弱,直接在 Raspberry Pi 上編譯 Rust 程式可能會比較慢。因此,我們需要在更強大的主機(如 x86 Linux PC)上進行跨平台編譯。
首先,我們需要在 x86 Linux PC 上安裝針對 Raspberry Pi 4 Model B 的編譯工具鏈。Raspberry Pi 4 Model B 使用 ARM Cortex-A72 CPU,因此我們需要安裝 armv7-unknown-linux-gnueabihf 目標。
rustup target add armv7-unknown-linux-gnueabihf
內容解密:
- 使用
rustup target add命令新增armv7-unknown-linux-gnueabihf目標。 - 由於 Raspberry Pi OS 預設為 32 位元,因此我們需要使用
armv7目標,即使 CPU 是 ARMv8 架構。
接下來,我們需要安裝連結器(linker)。在 Ubuntu 系統上,可以透過安裝 gcc-10-multilib-arm-linux-gnueabihf 套件來獲得 ARM 連結器。
sudo apt-get install gcc-10-multilib-arm-linux-gnueabihf
然後,我們需要在 ~/.cargo/config 檔案中設定連結器。
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc-10"
內容解密:
- 設定 Cargo 使用
arm-linux-gnueabihf-gcc-10作為armv7-unknown-linux-gnueabihf目標的連結器。
現在,我們可以建立一個新的 Cargo 專案,並使用 --target 引數進行跨平台編譯。
cargo new blink-cross-compile
cargo build --target=armv7-unknown-linux-gnueabihf
內容解密:
- 使用
cargo new建立新的專案。 - 使用
--target引數指定目標平台為armv7-unknown-linux-gnueabihf。
編譯完成後,我們可以將產生的二進位檔案複製到 Raspberry Pi 上執行。
8.4 GPIO 程式碼的運作原理
rust_gpiozero 套件抽象了大部分 GPIO 控制的複雜性。但我們仍然可以探討其底層的運作原理。
Sysfs 提供了一種虛擬檔案系統,用於存取 GPIO 暫存器。我們可以透過寫入 Sysfs 虛擬檔案來控制 LED。
echo "2" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio2/direction
echo "1" > /sys/class/gpio/gpio2/value
內容解密:
- 將 GPIO pin 2 輸出到 Sysfs。
- 設定 GPIO pin 2 的方向為輸出。
- 將 GPIO pin 2 的值設為高電位,開啟 LED。
本章節介紹瞭如何在 Raspberry Pi 上使用 Rust 進行實體運算,並探討了跨平台編譯的概念。透過使用跨平台編譯,我們可以在更強大的主機上編譯程式,然後在 Raspberry Pi 上執行。未來,我們可以進一步探討更多關於實體運算和跨平台編譯的技術。