Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello, Sensor!

大まかに言うと、これから作成するドライバーは、バイト列の形式でさまざまなコマンドをセンサーに送信できるようになります。コマンドに応じて、センサーはプロセスを開始または終了したり、データを返したりします。SCD30 は 3 つの異なるプロトコルを使用できますが、ここでは I2C を使用します。

この実装の例は、こちらで確認できます: 10_scd_30_log_v.rs

配線

SCD30 の配線用ブレッドボード図

前提条件

プログラム内で、次のリソースにアクセスできるようにしてください。

  • Timer
  • P0 Pins
  • 1 LED

I2C リソースのセットアップ

使用するリソースは twim と呼ばれます。Twim と I2C は同一のプロトコルで、違いは後者が商標登録されており、前者はそうではないという点です。

✅ 次のモジュールをスコープに取り込みます。

#![allow(unused)]
fn main() {

// ボードペリフェラルへのアクセス:
use nrf52840_hal::{
    self as hal,
    gpio::{
        p0::{
            Parts as P0Parts
        }, 
        Level,
    },
    prelude::*,
    Temp, 
    Timer,
    twim::{self, Twim, Error, Instance},
};
}

fn main() で、2 つのピンを floating として設定します。1 つはデータ信号(SDA)用、もう 1 つはクロック信号(SCL)用です。

#![allow(unused)]
fn main() {
let scl = pins.p0_30.degrade();
let sda = pins.p0_31.degrade();
}

✅ ピンを twim::Pins としてインスタンス化します。

#![allow(unused)]
fn main() {
let pins = twim::Pins { scl, sda };
}

Twim インスタンスを作成します。このメソッドは 3 つの引数を取ります: TWIM ペリフェラル、pins、周波数です。

#![allow(unused)]
fn main() {
let i2c = Twim::new(board.TWIM0, pins, twim::Frequency::K100);
}

✅ プログラムの最後に点滅ループを追加します。これは、プログラムが実行中であることを視覚的に出力する方法です。

#![allow(unused)]
fn main() {
loop {
        timer.delay(250_000);
        led_1.set_high().unwrap();
        timer.delay(250_000);
        led_1.set_low().unwrap();
    }
}

✅ プログラムを実行します。LED が点滅するはずです。

I2C インスタンスを構築したので、センサーのインターフェイスに接続する必要があります。そのためには、センサーのアドレスが必要です。

Interface Description でセンサーのアドレスを見つけ、fn main() の上にグローバル変数 DEFAULT_ADDRESS として追加します。

回答
```rust
pub const DEFAULT_ADDRESS: u8 = 0x61;
```

✅ センサー用のモジュール scd30 を作成します。 src/scd30/mod.rs 内に、Twim<T> の型エイリアスとして匿名構造体を作成します。

#![allow(unused)]
fn main() {
pub struct SCD30<T: Instance>(Twim<T>);

impl<T> SCD30<T> where T: Instance {
    /// impl ブロック
}
}

<T> とは何でしょうか?

I2C は Twim<T> という型を持っており、T はジェネリック型パラメーターで、struct 内で定義する必要があります。ジェネリック型 <T> が関数引数の型宣言の一部である場合、関数名の直後に指定する必要があります。その struct に対してメソッドを実装する場合も、<T> を指定して定義する必要がありますが、これは impl ブロックの開始行で行います。

impl ブロック内に、SCD30 のインスタンスを返す静的メソッドを作成します。

#![allow(unused)]
fn main() {
impl<T> SCD30<T> where T: Instance {

    pub fn init(i2c2: Twim<T>) -> Self {
        SCD30(i2c2)
    }

    /// その他のメソッド
}
}

次に、センサーインスタンス上で使用できるメソッドを作成します。このメソッドの目的は、センサーのファームウェアバージョン番号を読み取れるようにするコマンドをセンサーに書き込むことです。これを行うには、センサーの Interface Description でいくつかの情報を見つける必要があります。

✅ ファームウェアバージョンを読み取るための I2C コマンドを見つけます。

回答
I2C `0xD100`

✅ 実際にセンサーへ書き込む必要があるメッセージシーケンスを見つけます。

回答
Start 0xC2 0xD1 0x00 Stop

Start シンボルの後には、これが *write* メッセージであることを示すバイト `0xC2` があります。このバイトは、センサーのアドレス 0x61 を 1 ビット左シフトしたものです。これはこれから使用する `write()` メソッドにすでに実装されているため、今は無視できます。 

✅ センサーから読み取られるメッセージを見つけます。実際の情報内容の長さは何バイトですか?

回答 読み取られるメッセージ:
Start 0xC3 0x03 0x42 0xF3 Stop

*read* バイトに注目してください。これもセンサーのアドレスから 1 ビットシフトしただけのものです。*read* バイトの後には、3 バイトと Stop シンボルがあります。3 バイトのうち、最初がメジャーバージョン番号、2 番目がマイナーバージョン番号、最後が CRC バイトです。CRC は cyclic redundancy check の略で、生データの偶発的な変更を検出します。したがって、実際の情報の長さは 2 バイトです。

`read()` メソッドは、*read* バイトに続くすべてのバイトのバイト配列を返します。 

✅ 例にある 16 進数のバイト表現からバージョン番号を計算します。

回答
|0x03|0x42|
|----|----|
|3   |66  |

したがって、この例のバージョン番号は 3.66 です。

このメソッドについて詳しく見ていきます。他のすべてのメソッドは、このテーマのバリエーションにすぎないためです。

SCD30 の impl ブロックにメソッドを追加します。

#![allow(unused)]
fn main() {
pub fn get_firmware_version(&mut self) -> Result<[u8; 2], Error> {
    let command:[u8; 2] = [0xd1, 0x00];
    let mut rd_buffer = [0u8; 2];
        
    self.0.write(DEFAULT_ADDRESS, &command)?;
    self.0.read(DEFAULT_ADDRESS, &mut rd_buffer)?;

    let major = u8::from_be(rd_buffer[0]);
    let minor = u8::from_be(rd_buffer[1]);
        
    Ok([major, minor]) 
}
}

このメソッドは self への可変参照を受け取り、Error バリアントと、2 つの符号なし 8 ビット整数の array を含む ok バリアントを持つ Result 型を返します。

関数本体の最初の行では、センサーに送信されるコマンドを含む 2 つの u8array を作成します。次に、0 で初期化された 2 つの u8 を含む空の読み取りバッファを作成します。これは、返されるバイトのうち最初の 2 バイトだけが必要だからです。CRC バイトは省くことができます。

次に、SCD30 上で write() メソッドを呼び出します。このメソッドは、アドレスとコマンドへの参照を引数として取ります。その後、アドレスと読み取りバッファへの可変参照を引数として read() メソッドを呼び出します。

最後の処理は、返されたバイトを 10 進数に変換し、それらを配列として返すことです。

✅ プログラムファイルに移動し、scd30 モジュールをスコープに取り込みます。

#![allow(unused)]
fn main() {
use knurling_session_20q4::scd30;
}

fn main() でセンサーインスタンス上のメソッドを呼び出し、センサーのファームウェアバージョンをログに記録します。

#![allow(unused)]
fn main() {
let firmware_version = sensor.get_firmware_version().unwrap();
defmt::info!("Firmware Version: {:u8}.{:u8}", firmware_version[0], firmware_version[1]);
}

プログラムを実行します。LED が点滅している間に、ログ出力としてバージョン番号が得られるはずです。

おめでとうございます!ハードウェアドライバーの最初の部分を作成しました!