計測を開始する
❗️❗️❗️ defmt の更新
先週、defmt v0.1.0 の crates.io 版が crates.io でリリースされました。プロジェクトを github 版の defmt から crates.io 版へ移行するための便利なguideを用意しました。app-template をベースにした新しいプロジェクトは、今後自動的に crates.io 版を使用します。
古い手順のリファクタリング:
すべてをまとめるの章と Hello Sensor の章を読み、コードをモジュールに分けてください
ファームウェアのバージョン番号を読み取ってログに記録し、通信が正しくセットアップされていることを確認したら、計測を開始します。
この実装の例は、こちらで確認できます: 11_scd_30_measure.rs。
✅ Interface Description のセクション 1.4.1 に進んでください。
連続計測をトリガーするために提供する必要があるメッセージコンポーネントは何ですか?
回答
0x00 コマンドバイト
0x10 コマンドバイト
0x00 引数: 周囲気圧
0x00 引数: 周囲気圧
0x81 CRC バイト
開始記号と停止記号は write メソッドによって自動的に提供されます。
このメッセージにはコマンドだけでなく、周囲気圧の値を設定できる引数も含まれています。
周囲気圧の役割
気圧は温度とともに、定義された空間内にどれだけの気体分子が存在するかを決定します。圧力が上がると分子の数は増え、圧力が下がると減ります。CO2 に対するセンサーの出力単位は ppm、つまり parts per million であり、空気全体に含まれる 100 万個の粒子(原子または分子)のうち、一定数が二酸化炭素分子であることを意味します。
非常に正確なセンサー読み取り値が必要な場合、周囲気圧の値は別のセンサーから取得するべきです。職場や教室向けの空気質モニターを作る場合は、値をハードコードすれば十分です。海面での標準気圧は 1013.25 mbar です。標高の高い場所に住んでいる場合は、地域の気象台で値を確認してください。
このチュートリアルでは、ベルリンの現在値である 1020 mbar を使用します。
連続計測を開始する
✅ src/scd30/mod.rs に進んでください。impl SCD30 ブロック内に、引数として &mut self と pressure: u16 を受け取り、バリアント () と Error を持つ Result 型を返す新しい関数を追加します。
この関数内で、送信するメッセージの長さである 5 個の u8 バイト用の可変配列を定義します。引数バイトと crc バイトは 0x00 のままにします。
#![allow(unused)]
fn main() {
pub fn start_continuous_measurement(&mut self, pressure: u16) -> Result<(), Error> {
let mut command: [u8; 5] = [0x00, 0x10, 0x00, 0x00, 0x00];
// ...
Ok(())
}
}
次に、引数をコマンドに埋め込みます。センサー通信はビッグエンディアンのバイト順で動作します。
✅ u16 値をビッグエンディアンのバイト列に変換してください。返されたスライスに含まれる値を、コマンド内のそれぞれの位置に代入します。
#![allow(unused)]
fn main() {
let argument_bytes = &pressure.to_be_bytes();
command[2] = argument_bytes[0];
command[3] = argument_bytes[1];
}
CRC バイトの計算
2 バイトより長いメッセージを送信する場合、検証のために 2 バイトごとに CRC バイトを送信する必要があります。これらは引数バイトから計算する必要があります。
✅ cargo.toml に進み、次の依存関係を追加してください。
#![allow(unused)]
fn main() {
crc_all = "0.2.0"
}
✅ src/scd30/mod.rs に戻り、モジュールをスコープに取り込みます。
#![allow(unused)]
fn main() {
use crc_all::Crc;
}
✅ crc_all のドキュメントを確認してください。インスタンスメソッド Crc::<u8>::new() にはどの引数が必要ですか?
センサーの Interface Description のセクション 1.1.3 に進み、すべての引数を埋められるか確認してください。
回答
|引数|情報|
|-|-|
|poly: u8|0x31|
|width: uszise|8|
|init: u8|0xff|
|xorout: u8|0x00|
|reflect: bool|false|
✅ pub fn start_continuous_measurement() の中で、集めた情報を使って新しい crc バイトをインスタンス化します。この変数は可変である必要があります。pressure 値で crc バイトを更新し、そのバイトをコマンド配列内の位置に代入します。コマンドをセンサーに送信します。
#![allow(unused)]
fn main() {
let mut crc = Crc::<u8>::new(0x31, 8, 0xff, 0x00, false);
crc.update(&pressure.to_be_bytes());
command[4] = crc.finish();
self.0.write(DEFAULT_ADDRESS, &command)?;
}
✅ プログラムファイルに移動します。fn main() で周囲気圧を設定し、計測を開始してください!
#![allow(unused)]
fn main() {
// 1020_u16 を地域の値に置き換えてください
let pressure = 1020_u16;
// ...
sensor.start_continuous_measurement(pressure).unwrap();
loop {
///...
}
}
✅ プログラムを実行してください。LED が点滅するはずです。
電源投入後、センサーはデータを読み取れるようになるまで約 2 秒かかります。値を提供するだけでなく、センサーはデータがまだ準備できているかどうかの情報も提供できます。
✅ src/scd30/mod.rs で、data_ready メソッドを実装してください。コマンドと読み取りバッファの長さについては、インターフェース説明を確認してください。
回答
```rust
pub fn data_ready(&mut self) -> Result<bool, Error> {
let command: [u8; 2] = [0x02, 0x02];
let mut rd_buffer = [0u8; 3];
self.0.write(DEFAULT_ADDRESS, &command)?;
self.0.read(DEFAULT_ADDRESS, &mut rd_buffer)?;
Ok(u16::from_be_bytes([rd_buffer[0], rd_buffer[1]]) == 1)
}
```
✅ プログラムファイルで、点滅ループの前に新しいループを開き、データが準備できているかどうかをセンサーから継続的に読み取るようにします。メソッドが true を返したら “Data ready.” を出力するログ文を追加します。その後、ループを抜けます。
#![allow(unused)]
fn main() {
loop {
if sensor.data_ready().unwrap() {
defmt::info!("Data ready.");
break
}
}
✅ プログラムを実行してください。“Data ready” というログ出力が表示されるはずです。
センサーデータの読み取りとログ出力
センサーは 3 つの値を返します。1 つは二酸化炭素濃度、1 つは温度、1 つは湿度です。Interface Description のセクション 1.5 で、センサーがデータに使用する数値型を確認してください。
回答
センサーが返す値は、ビッグエンディアン形式の浮動小数点数です。
✅ src/scd30/mod.rs に進んでください。各値に対応するフィールドを持つ新しい struct 定義を追加します。
#![allow(unused)]
fn main() {
pub struct SensorData {
pub co2: f32,
pub temperature: f32,
pub humidity: f32,
}
pub const DEFAULT_ADDRESS: u8 = 0x61;
pub struct SCD30<T: Instance>(Twim<T>);
impl<T> SCD30<T>
where
T: Instance,
{
// ...
}
}
✅ impl SCD30 ブロック内に、新しいメソッドを追加します。
#![allow(unused)]
fn main() {
pub fn read_measurement(&mut self) -> Result<SensorData, Error> {
// ...
Ok(data)
}
}
- コマンドと読み取りバッファーの長さについては、Interface Descriptionを確認してください。
SensorDataのインスタンスを作成します。- 関連するバイトを
f32値に変換します。ビッグエンディアンのバイト列からf32への変換については、stdドキュメントを確認してください。 - データを返します。
解答
#![allow(unused)]
fn main() {
pub fn read_measurement(&mut self) -> Result<SensorData, Error> {
let command: [u8; 2] = [0x03, 0x00];
let mut rd_buffer = [0u8; 18];
self.0.write(DEFAULT_ADDRESS, &command)?;
self.0.read(DEFAULT_ADDRESS, &mut rd_buffer)?;
let data = SensorData {
co2: f32::from_bits(u32::from_be_bytes([
rd_buffer[0],
rd_buffer[1],
rd_buffer[3],
rd_buffer[4],
])),
temperature: f32::from_bits(u32::from_be_bytes([
rd_buffer[6],
rd_buffer[7],
rd_buffer[9],
rd_buffer[10],
])),
humidity: f32::from_bits(u32::from_be_bytes([
rd_buffer[12],
rd_buffer[13],
rd_buffer[15],
rd_buffer[16],
])),
};
Ok(data)
}
}
✅ プログラムファイル内の点滅ループの中で、このメソッドを呼び出し、値とその単位をログに追加してください。
#![allow(unused)]
fn main() {
loop {
let result = sensor.get_measurement().unwrap();
let co2 = result.co2;
let temp = result.temperature;
let humidity = result.humidity;
defmt::info!("
CO2 {=f32} ppm
Temperature {=f32} °C
Humidity {=f32} %
", co2, temp, humidity
);
// LEDを点滅させる
}
}
✅ プログラムを実行すると、ログ出力に3つの値が表示されるはずです。
オプションの課題:
- センサーの工場出荷時キャリブレーションはかなり優れていますが、センサーのキャリブレーション方法を調べ、必要なメソッドを実装することもできます。
- 高度補正メソッドを実装し、圧力補正の代わりに使用してください。
- センサーから取得した相対湿度値から絶対湿度を計算してください。
- 露点を計算してください。
- センサーの Interface Description に記載されている残りのメソッドを実装してください。