はつねの日記

Kinect, Windows 10 UWP, Windows Azure, IoT, 電子工作

M5Stackで.NET nanoFrameworkからセンサー使ってみた

前回

hatsune.hatenablog.jp

前回は、.NET nanoFrameworkでの開発準備とM5Stack Core 2での簡単なアプリの実行まで実施しました。
今回は、M5Stack Core 2のPORT Aと呼ばれる外部端子にセンサーを接続して、気圧や気温、湿度などを画面に表示してみたいと思います。

事前準備

センサーについて

この記事で使用するセンサーは、手元にあったM5StackユニットのENV-IIを使います。
M5Stackユニットは、M5Stack Core 2のPORT.A.I2C端子に接続できるセンサーです。
PORT.A.I2Cは、その名の通りI2Cと呼ばれるシリアスバス規格となります。
ENV-IIは、気圧センサーとしてBMP280、気温と湿度のセンサーとしてSHT30を内蔵したユニットとなります。
ENV-IIとM5Stack Core 2を接続し、更にM5Stack Core 2をUSBでPCに接続して、準備完了です。

Install the nanoFramework firmware

M5Stackに使われているESP32にはnanoFrameworkは標準インストールされていないので、nanoFrameworkをインストールします。

接続ポート番号の確認

USBでM5Stack Core 2をPCに接続したら、デバイスマネージャーで「CP210x」が接続されているポート番号を確認します。今回の環境では「COM4」が該当しました。

Flasherアプリケーションのインストール(インストール済の場合は不要)

OSの[スタート]メニューから「Developer PowerShell for VS2022」を起動します。

dotnet tool install --global nanoff

nanoFrameworkfファームウェアをインストール

今回使用するM5Stackは、M5Stack Core 2になります。
このM5Stack Core 2をターゲットにして「COM4」経由でnanoFrameworkをアップロードします。

nanoff --target M5Core2 --update --serialport COM4

Device Explorerでの接続確認

nanoFrameworkファームウェアをインストールしたあとにVisual Studioを起動してDevice Explorerをみると、「M5Core2 @ COM4」と表示されるようになります。

nanoFrameworkファームウェアは、インストールしたあとにnanoFramework以外の別イメージをM5Stack Core 2に転送(Burn)してしまうと消えてしまうので、Device Explorerに表示がないような場合は、再度、インストールをしてからVisual Studioを起動すれば表示されるようになります。

Visual Studioの準備

Visual Studioを機能拡張する

Visual Studioには.NET nanoFramework Extensionをインストールして機能拡張しておきます。
この機能拡張によりnanoFrameworkアプリのテンプレートが追加されます。
このテンプレートで作成したプロジェクトには、NuGetから自動的に「nanoFramework.CoreLibrary」ライブラリが追加されています。

「nanoFramework.M5Core2」ライブラリを追加する

今回は、M5Stack Core 2を使うので、「nanoFramework.M5Core2」ライブラリを手動でNuGetから追加します。

ここまでが、M5Stack Core 2を使うアプリ開発を行う上での共通の事前準備となります。

M5Stackで気圧表示(BMP280)

ライブラリの追加

.NET nanoFrameworkにはBMP280用のライブラリである「nanoFramework.Iot.Device.Bmxx80」がNuGetで提供されています。
.NET nanoFramrworkアプリケーションテンプレートで新規プロジェクトを作成したら、NuGetでBmxx80ライブラリを追加します。

BMP280から気圧を取得して画面に表示するC#コード

ENV-IIのI2Cアドレスを調べる

I2Cシリアル通信では、PORT.A.I2C端子につながっているセンサーから特定のセンサーを指定するために「アドレス」を指定します。
ENV-IIの中のBMP820と接続するためにアドレスに指定する値は、ENV-IIの仕様書をみると書かれています。
ENV II Unit with Temperature Humidity Environment Sensor (SHT30+BMP280) | m5stack-store

Description
ENV II is an environmental sensor which can sense temperature, humidity and atmospheric pressure. It integrates the SHT30 and BMP280 sensors and is programmed over the I2C protocol(SHT30:0x44, BMP280:0x76).

このアドレスを指定してI2C通信を行うことでBMP280より気圧値を取得できます。
nanoFrameworkでは、I2C通信用の「System.Device.I2c」クラスがあるので次のようなコードでI2C通信経路を確立できます。
gist.github.com

このコードは、「nanoFramework.M5Core2」ライブラリをNuGetから追加すると次のように簡単になります。
gist.github.com

センサー値を取得する

全体的なコードは次のようになります。
gist.github.com
センサー値の取得部分は次のところです

UnitsNet.Pressure pressure;
if (Bmp.TryReadPressure(out pressure))

[UnitsNet.Pressure」ライブラリは、「nanoFramework.IoT.Device.Bmxx80」ライブラリをNuGetで入れると自動的に追加されるBMP280で気圧を取得するためのライブラリです。
I2Cシリアル通信は、バイナリデータの送受信となりセンサー仕様のバイナリ形式から必要な数値形式に変換など地味にコード量が多くなる傾向にあります。
[UnitsNet.Pressure」ライブラリは、BMP280でのそのあたりを隠蔽して、更にヘクトパスカル表示(最近はミリバールとは言いません)のための単位変換もやってくれます。

M5Stackで気温と気圧表示

BMP280から気圧を無事取得できたので、次は、SHT30から気温と湿度を取得してみます。

ライブラリの追加

.NET nanoFrameworkにはSHT30用のライブラリである「nanoFramework.Iot.Device.Sht3x」がNuGetで提供されています。

SHT30から気温と湿度を取得して画面に表示するC#コード

ENV-IIのI2Cアドレスを調べる

ENV-IIのSHT30のアドレスは仕様書から「0x44」なのでそれを指定して通信を確立します。
gist.github.com
最近のC#では左辺の型がきまっているときはnewの後のクラス名を省略できるので下記のように書くこともできます。

Sht = new (M5Core2.GetI2cDevice(0x44))

センサー値を取得する

全体的なコードは次のようになります。
gist.github.com
センサー値の取得部分は次のところです

Console.WriteLine($"Temperature:{Sht.Temperature.DegreesCelsius:F} C");
Console.WriteLine($"Humidity:{Sht.Humidity.Percent:F} %");

M5Stack Core 2での動作

ENVIIから気圧、気温、湿度を取得するコードが完成したので、Visual Studioで[デバッグ]-[デバッグの開始]メニューでM5Stack Core 2への転送と実行を行います。

時間とセンサー値が1秒ごとに更新表示されます。

ENV-IIが入手できない

今回利用したENV-IIですが、残念ながら、2022年6月現時点では販売は終了しており、後継品のENV-IIIに代わっていました。
www.switch-science.com

ENV-IIとENV-IIIの違いを確認する

ENV-IIとENV-IIIの違いは気圧センサーがBMP280からQMP6988に変更されている点です。
そのため、ENV-II対応のプログラムからBMP280関連部分は変更が必要になります。

ENV-IIIのI2Cアドレスを調べる

I2Cシリアル通信では、PORT.A.I2C端子につながっているセンサーから特定のセンサーを指定するために「アドレス」を指定します。
ENV-IIIの中のQMP6988と接続するためにアドレスに指定する値をENV-IIIの仕様書から読み取ります。
ENV III Unit with Temperature Humidity Air Pressure Sensor (SHT30+QMP6988) | m5stack-store

I2C interface (SHT30:0x44 , QMP6988:0x70)

SHT30についてはENV-IIと同様に「0x44」ですが、QMP6988は「0x70」となります。

ENV-IIIのライブラリを調べる

SHT30については「nanoFramework.Iot.Device.Sht3x」ライブラリが使えますが、QMP6988を使うためのライブラリは2022年6月現時点ではNuGetに公開されていません。
そこで自作でQMP6988用のクラスを作成する必要があることが分かりました。

QMP6988のデータシートを読み解く

QMP6988用のクラスを作成するためには、必要なQMP6988の情報をデータシートで確認することになります。
https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf
データシートを読んでみるとQMP6988は気圧以外にも気温が取得できるようです。この気温は、気圧の補正に使用します。
データシートには、そのほかにもQMP6988の気圧解像度(正確性)が0.06 Pa = 6 hPaであるなどの情報も記載されている。気圧解像度が6 hPaということは、6 hPa以下の精度は出ないということになります。

センサー値の理解

QMP6988はレジスタと呼ばれる1byte(=8bit)の領域を介して値のやりとりを行います。
データシートの「4.4 Implementing Register List」によればレジスタは全部で38個あります。

気圧データは0x07~0x09の3つのレジスタ、つまり3byte値で取得できます。
3byteの中のデータフォーマットには、22bitモード、23bitモード、24bitモードの3つのデータフォーマットがあります。
データ形式は、アドレスが小さい方に上位ビットが来る「BigEndian」形式となります。

センサーの生データは「符号なし24bitデータ」とのことで、データシートによれば24bitモードでは2の23乗をマイナスする必要があると書かれています。
このあたりのbitフォーマットからの数値化の方法も記載があり次のようにすればよいことが分かります。

Dp = ((PRESS _ TXD2) <<16) + ((PRESS _ TXD1) << 8) + (PRESS _ TXD0) − pow(2,23)

.NET nanoFrameworkには便利な関数が用意されておりシフト演算をしなくても「BigEndian」形式からuint型へ変換してくれます。

System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(data) - Math.Pow(2, 23)

QMP6988の設定

QMP6988の設定初期は以下の通りです。この設定は、CTRL_MEASレジスタ(0xF4)により変更できます、

  • 出力モード:測定しない
  • パワーモード:Sleep


QMP6988には3種類の出力モードがあり、パワーモードをNormalにして、気温は22bitモード、気圧は24bitモードで出力したいときは、「0x1F」を設定します。
気温を22bitモードにしているのは、気圧補正用の気温のため精度はそれほど重要でないため一番低い解像度でよいからです。

パワーモードについて

パワーモードを変更すると計測と消費電力の関係は次のようになります。

  • Sleep:消費電力は最小ですが、測定はできません。
  • Force: 測定時だけ測定用に回路に通電し、測定が終わると必要最小限の消費電力に落とすモードです。
  • Normal:定期的に測定しつづけるモードです。測定間隔の初期値は1msとなります。

測定間隔は、IO_SETUPレジスタのt_standby領域で指定が可能で、1ms~4秒までの間で指定ができて、測定間隔が長いほどトータル消費電力は下がります。もし、4秒よりも長い測定間隔でよい場合は、パワーモードをForceにするのが適切です。

QMP6988用クラスのC#コード

I2Cシリアル通信を確立する

IC2シリアル通信の確立は、QMP6988変わっても同様に「M5Core2.GetI2cDevice(0x70)」で実現できます。
BMP280用ライブラリと同様に、この戻り値をQMP6988クラスに渡します。
gist.github.com

モードを指定する

解像度やパワーモードを指定するためには、CTRL_MEASレジスタ(0xF4)に1バイトのデータを書き込むことで実現します。
gist.github.com

気圧を読み取る(3バイト領域)

QMP6988が気圧をレジスタに代入するタイミングは2つあります。
パワーモードがNormalの場合は、t_standbyに設定された間隔で自動的にレジスタに気圧が記録されますので、値を取得するときはレジスタにすでに格納されている値を読み取るのみです。
Forceの場合は、レジスタ読み取り時に気圧を測定してレジスタに格納します。
レジスタから値を取得するには、

  1. レジスタアドレスをWriteする
  2. 返却サイズに応じたバイト配列を指定してReadする

ことで3バイトの気圧データを取得できます。

気圧を読み取る(4バイト領域)

しかし、C#の変数で3バイト長の変数は存在しないため、少しおさまりが悪いです。そこで4バイト領域に3バイト分のデータを読み込みます。
gist.github.com
このコードの中で少し馴染みの薄い部分は次のところでしょう。

this.I2c.Read(readBuff.Slice(1))

この記述により合計で3バイトのデータをbyte[4]の2バイト目以降に受信します。
こうして得られたbyte[4]のバイト配列は次のコードでuint型の数値にできます。

System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(data)

気圧の生データを実地に変換

取得できた気圧の生データは、そのままでは正しい気圧値を表していないため、気温と合わせて補正計算をしてから16分の1の数値にします。
gist.github.com
BMP280もそうでしたが気圧センサーのデータ取り扱いの肝は「ConvTx02e」「Pressure02e」などで記載している補正関数になりますが、今回は説明が冗長になるので詳細は割愛します。

ENV-IIIから気圧、気温、湿度を取得して画面に表示するC#コード

QMP6988用のクラスが作成できたら、それを使ってENV-II用のコードを書き換えてENV-III用のアプリを作成しましょう。

ENV-IIIのI2Cアドレスを調べる

ENV-IIIのQMP6988のアドレスは仕様書から「0x70」なのでそれを指定して通信を確立します。

Qmp = new (M5Core2.GetI2cDevice(0x70))

センサー値を取得する

全体的なコードは次のようになります。
gist.github.com
センサー値の取得部分は次のところです

Console.WriteLine($"DateTime:{DateTime.UtcNow:yyyyMMdd HHmmss}");
Console.WriteLine($"Pressure:{Qmp.CalcPressureHectopascals():F} hPa");
Console.WriteLine($"Temperature:{Sht.Temperature.DegreesCelsius:F} C");
Console.WriteLine($"Humidity:{Sht.Humidity.Percent:F} %");

M5Stack Core 2での動作

ENV-IIIから気圧、気温、湿度を取得するコードが完成したので、Visual Studioで[デバッグ]-[デバッグの開始]メニューでM5Stack Core 2への転送と実行を行います。

時間とセンサー値が1秒ごとに更新表示されます。

最後に

M5Stack Core 2にENV-IIユニットやENV-IIIユニットを繋いでC#で取得・表示するコードを紹介しました。
ユニットに使われているセンサーチップに対応したライブラリがNuGetにあると非常に簡単にコードが書けます。
そしてライブラリがない場合もデータシートの読みポイントさえ見つけることができれば、バイナリーデータ送受信とフォーマット変換のコードへとデータシートの内容を読み落としていく工程は、簡単とはいえませんが不可能ではありません。
NuGetにライブラリがないからと言って使うのをあきらめたりせず、データシートを読み解きながら実装していくのも、センサー系開発の醍醐味です。

センサーの取り扱いが整理できて来たので、次回は、今回取得したセンサーデータをAzureに送信するコードに挑戦します。
hatsune.hatenablog.jp