【Rust】Raspberry PiのGPIOやSPIを操作するCrateまとめ

# はじめに
Raspberry PiのGPIOやSPIを操作するCrateがいろいろとありますので紹介します。
同様のものをGitHubに上げてますので、サンプルコードのプロジェクト一式が欲しいから以下のURLからどうぞ。

https://github.com/hukatama024e/raspi_crate_sample

# 動作環境
* Raspberry Pi 3 Model B+
* raspbian 8.0
* Rust 1.27.0

# 準備
SPIを使うため、以下の手順でSPIを有効にする必要があります(一連の作業にはroot権限が必要)。
1. `raspi-config`でSPIを有効設定にするか、`/boot/config.txt`に`dtparam=spi=on`を追加。
2. `reboot`などでRaspberry Piを再起動。

# サンプルコード仕様
サンプルコードの回路構成・動作仕様は以下の通りです。

## 回路構成
以下の図のようにGPIO24にLEDを接続しています。また、MISOとMOSIを直接接続しています。

![Circuit.png](https://qiita-image-store.s3.amazonaws.com/0/86057/ef54a228-65c4-cc52-9537-739370a132cd.png)

## 動作仕様
1. LEDの点滅を10回行う。
2. SPIをサポートしている場合はデータを送受信する。

SPIの設定ですが、直接接続しているので制約があるわけではないのですが以下の表のように設定しています。

| 項目| 設定内容 |
|:----:|:----:|
| Slave Select pin| SPI0_CE0 |
| クロック速度 | 62.5MHz |
| 動作モード | Mode0(CPOL=0、CPHA=0) |
| Slave Select極性 | Active Low |

# Crate
Crate毎にサンプルコード、crates.ioのURL、Lincenseを記載します。

各Crateが持つ機能は以下の表の通りです。

| | rppal | rustpi_io | sysfs_gpio | wiring_pi |
|:----:|:----:|:----:|:----:|:----:|
| GPIO | 〇 | △ | △ | 〇 |
| SPI | 〇 | 〇 | - | - |
  
〇: サポートしていてかつroot権限は不要
△: サポートしていますがroot権限が必要
-: サポートされていません

## rppal
rppalはGPIOとSPIをサポートしています。
また、最近のUpdate(v0.7)でI2Cがサポートされるなど、活発に開発されています。
迷ったらrppalを使ってみるのがいいかと思います。

**Lincese**
MIT

**crates.io**
https://crates.io/crates/rppal

**サンプルコード**

```rust
extern crate rppal;

use std::thread;
use std::time::Duration;
use rppal::{gpio, spi};
use rppal::gpio::{Gpio, Level};
use rppal::spi::{Spi, Bus, SlaveSelect};

// GPIOピン番号
const GPIO_PIN_NUM : u8 = 24;

// LED点滅回数
const BLINK_CNT : u8 = 10;

fn main() {
    let mut gpio = Gpio::new().expect( "Failed Gpio::new" );
    let mut blinking_cnt = 0;

    // GPIOモードを出力に設定
    gpio.set_mode( GPIO_PIN_NUM, gpio::Mode::Output );

    // LED点滅
    while blinking_cnt < BLINK_CNT {
        gpio.write( GPIO_PIN_NUM, Level::High );
        thread::sleep( Duration::from_secs( 1 ) );

        gpio.write( GPIO_PIN_NUM, Level::Low );
        thread::sleep( Duration::from_secs( 1 ) );
        blinking_cnt = blinking_cnt + 1;
    }

    // GPIOモードを入力に戻す
    gpio.set_mode( GPIO_PIN_NUM, gpio::Mode::Input );

    // SPI設定
    let spi = Spi::new( Bus::Spi0, SlaveSelect::Ss0, 6_2500_000, spi::Mode::Mode0 )
                    .expect( "Failed Spi::new" );

    // SPIデータ送受信
    let write_data = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
    let mut read_data = [0; 8];
    let read_size = spi.transfer( &mut read_data, &write_data ).expect( "Failed Spi::transfer" );

    for i in 0..read_size {
        println!( "read_data[{}] = 0x{:X}", i, read_data[i] );
    }
}
```

## rustpi_io
rustpi_ioはGPIOとSPIをサポートしています。
ただし、GPIOはsysfs(/sys/class/gpio/)経由でアクセスしているためroot権限が必要です。

**Lincese**
GPL-3.0

**crates.io**
https://crates.io/crates/rustpi_io

**サンプルコード**

```rust
extern crate rustpi_io;

use std::io::{Read, Write};
use std::thread;
use std::time::Duration;
use rustpi_io::gpio::{GPIO, GPIOData, GPIOMode};
use rustpi_io::serial::{SerialPi, Device, Speed, SpiMode, ComMode};

// GPIOピン番号
const GPIO_PIN_NUM : u8 = 24;

// LED点滅回数
const BLINK_CNT : u8 = 10;

fn main() {
    let gpio = GPIO::new( GPIO_PIN_NUM, GPIOMode::Write ).expect( "Failed GPIO::new" );
    let mut blinking_cnt = 0;
    
    // LED点滅
    while blinking_cnt < BLINK_CNT {
        gpio.set( GPIOData::High ).expect( "Failed GPIO.set" );
        thread::sleep( Duration::from_secs( 1 ) );

        gpio.set( GPIOData::Low ).expect( "Failed GPIO.set" );
        thread::sleep( Duration::from_secs( 1 ) );
        blinking_cnt = blinking_cnt + 1;
    }

    // SPI設定
    let mut spi = SerialPi::new( Device::CE0, Speed::Mhz62_5, SpiMode::Mode0, ComMode::FullDuplex )
                    .expect( "Failed SerialPi::new" );

    // SPIデータ送信
    let mut write_data = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
    spi.write( &mut write_data ).expect( "Failed SerialPi::write" );

    // SPIデータ受信
    let mut read_data = [0; 8];
    let read_size = spi.read( &mut read_data ).expect( "Failed SerialPi::read" );

    for i in 0..read_size {
        println!( "read_data[{}] = 0x{:X}", i, read_data[i] );
    }
}
```

## sysfs_gpio
sysfs_gpioはsysfsでGPIOにアクセスするCrateです。
そのため、rustpi_ioと同様にroot権限が必要です。

**Lincese**
MIT/Apache 2.0

**crates.io**
https://crates.io/crates/sysfs-gpio

**サンプルコード**

```rust
extern crate sysfs_gpio;

use std::thread;
use std::time::Duration;
use sysfs_gpio::Pin;
use sysfs_gpio::Direction;

// GPIOピン番号
const GPIO_PIN_NUM : u64 = 24;

// LED点滅回数
const BLINK_CNT : u8 = 10;

// GPIO設定値
const LOW : u8  = 0;
const HIGH : u8 = 1;


fn main() {
    let pin = Pin::new( GPIO_PIN_NUM );
    let mut blinking_cnt = 0;

    pin.with_exported( || {
        
        // GPIOモードを出力に設定
        pin.set_direction( Direction::Out ).expect( "Failed Pin::set_direction" );

        // LED点滅
        while blinking_cnt < BLINK_CNT {
            pin.set_value( LOW ).expect( "Failed Pin::set_value" );
            thread::sleep( Duration::from_secs( 1 ) );

            pin.set_value( HIGH ).expect( "Failed Pin::set_value" );
            thread::sleep( Duration::from_secs( 1 ) );
            blinking_cnt = blinking_cnt + 1;
        }
        
        // GPIOモードを入力に戻す
        pin.set_direction( Direction::In ).expect( "Failed Pin::set_direction" );

        Ok( () )
    } ).expect( "Failed Pin::with_exported" );
}
```

## wiringpi
wiringpiはC言語のライブラリ「Wiring Pi」のラッパーでGPIOをサポートしています。
現在の「Wiring Pi」の方はBCMのピン番号の方に対応しているので他のCrateと同じようなピン番号の指定を行うことが出来ますが、このCrateを使う場合はWiring Pi固有のピン番号を指定する必要があります。

**Lincese**
MIT

**crates.io**
https://crates.io/crates/wiringpi

**サンプルコード**

```rust
extern crate wiringpi;

use std::thread;
use std::time::Duration;
use wiringpi::pin::Value::{High, Low};

// GPIO24のWiring Pi固有のピン番号
const WPI_GPIO24_PIN_NUM : u16 = 5;

// LED点滅回数
const BLINK_CNT : u8 = 10;

fn main() {
    let wiringpi = wiringpi::setup();
    let pin = wiringpi.output_pin( WPI_GPIO24_PIN_NUM );
    let mut blinking_cnt = 0;
    
    // LED点滅
    while blinking_cnt < BLINK_CNT {
        pin.digital_write( High );
        thread::sleep( Duration::from_secs( 1 ) );

        pin.digital_write( Low );
        thread::sleep( Duration::from_secs( 1 ) );
        blinking_cnt = blinking_cnt + 1;
    }
}
```