Hello, Sensor!
大まかに言うと、これから作成するドライバーは、バイト列の形式でさまざまなコマンドをセンサーに送信できるようになります。コマンドに応じて、センサーはプロセスを開始または終了したり、データを返したりします。SCD30 は 3 つの異なるプロトコルを使用できますが、ここでは I2C を使用します。
この実装の例は、こちらで確認できます: 10_scd_30_log_v.rs。
配線

前提条件
プログラム内で、次のリソースにアクセスできるようにしてください。
- 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 つの u8 の array を作成します。次に、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 が点滅している間に、ログ出力としてバージョン番号が得られるはずです。
おめでとうございます!ハードウェアドライバーの最初の部分を作成しました!