Knurling Sessions 2020Q4 をやる (3) 〜CO2センサドライバ〜

はじめに

knurling-rs では Ferrous Systems が、スポンサー向けに組込み Rust の教材 knurling-session を提供しています。

github.com

knurling-session では、3ヶ月ごとに1つのテーマを取り上げています。 最新のもの以外は、公開されています。スポンサーになると最新のものも見れます。 また、スポンサーが増えると、knurling-rs に割く時間が増やせるため、ツールの改善や教材の整備が増えることが期待できます。 興味を持ったらスポンサーしましょう!

現在公開されている knurling-session の 2020 Q4 をやっていきます。 2020 Q4 は、nRF52840-DK と CO2 センサーを使ったプロジェクトです。

2020 Q4 のレンダリングされているドキュメントはこちら。

session20q4.ferrous-systems.com

前回、Lチカ終わらせたので、いよいよ CO2 センサ (SCD30) ドライバを書いていきます。

tomo-wait-for-it-yuki.hatenablog.com

Hello, Sensor!

まずは、CO2 センサの配線です。スルーホール用のテストワイヤ使ってみました。

f:id:tomo-wait-for-it-yuki:20210501171508p:plain
CO2センサの接続

SCD30 は、I2C インタフェースで制御します。 I2C ドライバのオブジェクトを作成して、SCD30 ドライバのオブジェクトを初期化します。

    let scl = pins.p0_30.degrade().into_floating_input();
    let sda = pins.p0_31.degrade().into_floating_input();
    let twi_pins = twim::Pins{ scl, sda };
    let i2c = Twim::new(board.TWIM0, twi_pins, twim::Frequency::K100);
    let mut scd30 = SCD30::init(i2c);

SCD30 ドライバも自作します。 データシートを見て、firmware version を取得するコードを書きます。

pub struct SCD30<T: Instance>(Twim<T>);

impl<T> SCD30<T> where T: Instance {
    pub fn init(i2c: Twim<T>) -> Self {
        SCD30(i2c)
    }

    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])
    }
}

これで firmware version が取得できるようになります。

       0 INFO  Firmware Version: 3.66

Start Measuring

データシートを見ながら、SCD30 ドライバにメソッドを追加していきます。 今回作成する SCD30 ドライバは、70 行くらいで、それほど複雑ではありません。

impl<T> SCD30<T> where T: Instance {
    pub fn start_continuous_measurement(&mut self, pressure: u16) -> Result<(), Error> {
        let mut command: [u8; 5] = [0x00, 0x10, 0x00, 0x00, 0x00];
        let argument_bytes = &pressure.to_be_bytes();
        command[2] = argument_bytes[0];
        command[3] = argument_bytes[1];

        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)?;

        Ok(())
    }

    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)
    }

    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 co2 = f32::from_bits(u32::from_be_bytes([rd_buffer[0], rd_buffer[1], rd_buffer[3], rd_buffer[4]]));
        let temperature = f32::from_bits(u32::from_be_bytes([rd_buffer[6], rd_buffer[7], rd_buffer[9], rd_buffer[10]]));
        let humidity = f32::from_bits(u32::from_be_bytes([rd_buffer[12], rd_buffer[13], rd_buffer[15], rd_buffer[16]]));

        let data = SensorData{ co2, temperature, humidity };
        Ok(data)
    }

ここまでできると、defmt 経由で計測結果を出力します。

f:id:tomo-wait-for-it-yuki:20210501171053p:plain
CO2

Red Alert!

CO2 センサ、RGB LED、ブザーを組み合わせてプログラムを作成します。