在處理嵌入式系統時常會需要控制 GPIO。所謂的 GPIO(General-Purpose I/O),就是 IC 的某些 pin 腳,可以透過軔體設定為「input(偵測外部的訊號是高電位還是低電位)」或是「output(對外輸出高電位或低電位)」狀態。
理論上來說,當 GPIO 設定為 input 狀態時,叫它「輸出高電位」是沒有意義的。反之,設定為 output 時,去讀取 pin 的電位狀態可能也是沒意義的。因此在設計存取 GPIO 的 API 時,最好能有適當的防呆機制。
有一種做法,是建立一個 struct,以 data 的方式儲存每根 pin 目前的狀態。然後每次要呼叫 set_bit()
或是 is_bit_set()
之類的函式時,進行 run-time checking。但一來 run-time checking 需要執行時間,二來儲存狀態需要用到記憶體,因此不算完美的解決方案。
而 Rust Embedded Book 中提出一個方案(也是目前相關 crates 中採用的方案),是利用 generic type,將 GPIO 的不同狀態設定為不同的型別參數,產生不同的型別。然後再針對特化版的型別,提供不同的 methods,以防止 GPIO 呼叫到不該呼叫到的 methods。
直接看程式碼可能比較清楚:
/// GPIO interface
struct GpioConfig<ENABLED, DIRECTION> {
periph: GPIO_CONFIG, // 實際用來控制硬體暫存器的界面
enabled: ENABLED, // 這根 GPIO 是否啟用?
direction: DIRECTION, // 輸入還是輸出?
}
// 可以用在 GpioConfig 欄位上的狀態型別
struct Disabled;
struct Enabled;
struct Output;
struct Input;
struct DontCare;
/// 不管 GPIO 的狀態為何,都可以使用這個版本的實作
impl<EN, DIR> GpioConfig<EN, DIR> {
// 關閉這根 GPIO 的功能
pub fn into_disabled(self) -> GpioConfig<Disabled, DontCare> {
/*.. 控制硬體暫存器,略。 ..*/
GpioConfig { // 回傳新的 GpioConfig 實體
periph: self.periph,
enabled: Disabled, // enabled 的型別設為 Disabled.
direction: DontCare, // 反正都是 Disabled 了,所以不重要。
}
}
// 打開這根 GPIO 的功能,並切換到 Input 模式
pub fn into_enabled_input(self) -> GpioConfig<Enabled, Input> {
/*.. 控制硬體暫存器,略。 ..*/
GpioConfig { // 回傳新的 GpioConfig 實體
periph: self.periph,
enabled: Enabled, // enabled 的型別設為 Enabled.
direction: Input, // direction 的型別設為 Input.
}
}
// 打開這根 GPIO 的功能,並切換到 Output 模式
pub fn into_enabled_output(self) -> GpioConfig<Enabled, Output> {
/*.. 控制硬體暫存器,略。 ..*/
GpioConfig { // 回傳新的 GpioConfig 實體
periph: self.periph,
enabled: Enabled, // enabled 的型別設為 Enabled.
direction: Output, // direction 的型別設為 Ouput.
}
}
}
/// 只有特化為 Output 模式的 GPIO 才能使用這些函式
impl GpioConfig<Enabled, Output> {
// 設定輸出電位為 high 或 low
pub fn set_bit(&mut self, set_high: bool) {
/*.. 控制硬體暫存器,略。 ..*/
}
}
/// 只有特化為 Input 模式的 GPIO 才能使用這些函式
impl GpioConfig<Enabled, Input> {
// 檢查輸入電位為 high 或 low
pub fn bit_is_set(&self) -> bool {
/*.. 控制硬體暫存器,略。 ..*/
}
}
fn gpio_example() {
// 取得某個尚未設定的 GPIO
let pin: GpioConfig<Disabled, _> = get_gpio();
// 下面這行編譯不會過,因為 pin 還是 Disabled 的狀態。
// let pin_status = pin.bit_is_set();
// 把 pin 轉成 Input。執行完這行後 pin 就不能用了。
let input_pin = pin.into_enabled_input();
// 此時就可以透過 input_pin 檢查狀態。
let pin_status = input_pin.bit_is_set();
// 試著透過 input_pin 設定輸出電位。編譯不會過。
// input_pin.set_bit(true);
// 把 input_pin 轉成 Output。執行完後 input_pin 也不能用了。
let output_pin = input_pin.into_enabled_output();
// 透過 output_pin 設定輸出電位。
output_pin.set_bit(true);
}