もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f
WinIoT on ラズパイでのI2C通信関連
- [C#/WinIoT/I2C] ラズパイ+WindowsIoTCore+C# で9軸センサ(MPU-9150)の値をとる
やったこと
電子工作で、ラズパイ3にWindows IoT Coreを入れて、サーマルカメラをつなげて、いわゆるサーモグラフィを作ってみたい。
さっと調べたところ、一番安くて(amazonで7000円くらい)手に入りやすそうな「MLX90640」を使おうと思うが、そのサーマルカメラがI2C接続のようなので、以前、9軸センサで練習したI2Cを生かせそう。
と思い立って作ったのが下記のようなもの。作るうえでいろいろ調べた事、やったことをメモしておく。
■動画
https://youtu.be/cPm-WGY2Y8c
■コード一式
https://github.com/tera1707/ThermalCamera
使った機材
- RaspberryPi3
- ラズパイ用小型モニタQuimat 3.5インチ
- サーマルカメラMLX90640
全体の流れ
- 回路作成
- サーマルカメラとのI2C通信実装
- VisualStudio2019ソリューションの設定
- I2C通信のデータ送受信の準備
- 初期化処理
- EEPROM読み出し
- サーマル生データを取得
- 生データから温度に変換
- 温度データからサーマル画像作成
回路作成
サーマルカメラとのI2C通信実装
以前実験した9軸センサのI2Cをもとに、通信部分の実装を行う。
VisualStudio2019ソリューションの設定
9軸センサのI2Cの方で同じ作業をしているので、そちら参照。
I2C通信のデータ送受信の準備
データシートによると、このサーマルカメラは、2バイトを一単位としてデータのやり取りを行う。
9軸センサ(MPU-9150)の場合は、1バイト単位でデータをやり取りしていたので、そこが違う。
9軸センサの場合は、下記のようにしていた。
// 書き込み:1バイト目に書き込みたいレジスタアドレス、2バイト目に書く内容を載せて送信WriteBuf=newbyte[]{0x6B,0x00};I2CAccel.Write(WriteBuf);// 読み込み:1バイト目に読み込みたいレジスタアドレスを載せておくると、// そのアドレスを先頭にした、指定バイト数分のデータが返ってくる// (バイト数の指定は、ReadBufの配列数で行う(下記の場合はnew byte[1]なので1バイト))WriteBuf=newbyte[]{0x75};ReadBuf=newbyte[1];I2CAccel.WriteRead(WriteBuf,ReadBuf);
今回のサーマルカメラでは、下記のようなメソッドを作って、ushortでアドレス指定、データ指定を行うようにした。
// 書き込みprivatevoidWriteRegisterData(ushortwriteAddr,ushortdata){// 書き込むデータ作成(最初の2バイトが書き込み先アドレス、その後の2バイトがそこに書き込むデータ)varwriteByteData=newbyte[]{(byte)(writeAddr/0x100),(byte)(writeAddr%0x100),// 書き込み先アドレス(byte)(data/0x100),(byte)(data%0x100),// 書き込みデータ};// 書き込み実施I2CThermalCamera.Write(writeByteData);}// 読み込みprivateushort[]ReadRegisterData(ushortreadAddr,intNumberOfData){// 返すデータ(受信したbyteデータをushortに直したもの)ushort[]ret=newushort[NumberOfData];// アドレスを上位/下位に分解vardestAddr=newbyte[]{(byte)(readAddr/0x100),(byte)(readAddr%0x100)};// 受信用バッファを確保(このサーマルカメラのレジスタは1つで2バイト)varreadBuf=newbyte[NumberOfData*2];// 読み込み実施I2CThermalCamera.WriteRead(destAddr,readBuf);// 読み込んだbyteデータをushortに直すfor(inti=0;i<NumberOfData;i++){ret[i]=(ushort)(readBuf[2*i]*0x100+readBuf[2*i+1]);}returnret;}
初期化処理
下記のような流れで初期化を行う。
(というか、I2cDevice.FromIdAsync();
まではWinIotのI2C通信の準備)
publicasyncTaskInitThermalCamera(){// すべてのI2Cデバイスを取得するためのセレクタ文字列を取得stringaqs=I2cDevice.GetDeviceSelector(I2cDeviceName);DeviceInformationCollectiondis=null;try{// セレクタ文字列を使ってI2Cコントローラデバイスを取得dis=awaitDeviceInformation.FindAllAsync(aqs);if(dis.Count==0){Debug.WriteLine("I2Cコントローラデバイスが見つかりませんでした");return;}}catch(Exceptionex){Console.WriteLine(ex.Message);throw;}// I2Cアドレスを指定して、デフォルトのI2C設定を作成するvarsettings=newI2cConnectionSettings(ThermalCameraI2CAddress);// バス速度を設定(FastMode:400 kHz)(指定しないと、標準設定(StandardMode:100kHz)になる)settings.BusSpeed=I2cBusSpeed.FastMode;// 取得したI2Cデバイスと作成した設定で、I2cDeviceのインスタンスを作成I2CThermalCamera=awaitI2cDevice.FromIdAsync(dis[0].Id,settings);if(I2CThermalCamera==null){Debug.WriteLine(string.Format("スレーブアドレス {0} の I2C コントローラー {1} はほかのアプリで使用されています。他のアプリで使用されていないか、確認してください。",settings.SlaveAddress,dis[0].Id));return;}// サーマルカメラの設定try{// コントロールレジスタを取得varctrreg=ReadRegisterData(0x800D,1).FirstOrDefault();// リフレッシュレートを変更する(現在のコントロールレジスタを読み出して、そいつに対して変更を実施)varctrregset=(ushort)(ctrreg|0x0300);WriteRegisterData(0x800D,ctrregset);}catch(Exceptionex){Debug.WriteLine("デバイスとの通信に失敗しました。: "+ex.Message);return;}// EEPROM読み出しthis.MLX90640_DumpEE();}
ここで「リフレッシュレートを変更」しているが、今回は32Hzを使用した。(最速の64Hzにしなかったことに特に理由なし。)
試したところ、リフレッシュレートの設定によって、下のデータ取得のところで出てくる「isReady」フラグ(0x8000のbit3)がONになるまでの時間が変わってくるので注意。
※以降の内容について
ここから下は、ほぼサンプルプログラムを基に作成しています。
⇒https://github.com/sparkfun/SparkFun_MLX90640_Arduino_Example
サンプルはC言語で描かれていますので、それをもとに、C#の処理を作成しました。
また、EEPROMの個別の中身や生データから温度に変換する計算など、データシートをじっくり読めばわかるのかもしれませんが、今回はあまり理解せずに(というか難しくてすぐに理解できなかった)サンプルを基に作らせて頂いてます。
EEPROM読み出し
上の初期化の中の一番下、下記の部分。
// EEPROM読み出しthis.MLX90640_DumpEE();
EEPROMに保存されているパラメータが、読み出したサーマルデータの生データを温度の値に変換する際に使われるので、EEPROMから各種パラメータを読みだして、所定のクラスに格納しておく。(ここではParamsMLX90640
クラスにいれた。)
データシートより、EEPROMはレジスタ0x2400番地から0x273F番地。
※EEPROMの読み出し項目は非常に多数あるので、コードは、githubのこちらを参照。
サーマル(生)データを取得 ~ 生データを温度データに変換
まずは、I2Cでサーマルの生データを取得する。
生データは温度データではないので、EEPROMから読み出したパラメータを使って温度に変換が必要。
データ取得は、データシートの「Measurement Flow」の項の流れに沿った処理を行う。
publicdouble[]GetTemperatureData(){for(inti=0;i<2;i++){byteisReady=0;while(isReady==0){// ステータスレジスタ取得isReady=(byte)(ReadRegisterData(0x8000,1).FirstOrDefault()&0x0008);}//// ステータスレジスタ書き込み(MeasurementStartをON)WriteRegisterData(0x8000,0x0030);// アIRデータ取得// 0x0400~0x06FF:IRデータ// 0x0700~0x070F:Ta_Vbe、CP.GAIN// 0x0720~Ta_PATA,CP,VddPixvarframeDataS=ReadRegisterData(0x0400,FrameDataLength);// ステータスレジスタ読み出し(SubPage番号)//ReadRegisterData(0x8000, 1);StatusRegister=(ushort)(ReadRegisterData(0x8000,1).FirstOrDefault()&0x0001);// コントロールレジスタ読み出しControlRegister=ReadRegisterData(0x800D,1).FirstOrDefault();/////////////////////////// データ読み出し終了、データから温度への変換計算実施/////////////////////////varta=this.MLX90640_GetTa(frameDataS,CamParameters);doubletr=ta-8;double[]ret=newdouble[FrameDataLength];// 生データを温度データに変換MLX90640_CalculateTo(frameDataS,CamParameters,0.95,tr,ret);for(intl=0;l<frameDataS.Length;l++){if(ret[l]>0.0){TotalFrameData[l]=ret[l];}}}returnTotalFrameData;}
生データ取得
メソッドMLX90640_CalculateTo()
より上は、生データ取得処理。
生データを取る際、for分で2回回している。
データを採る際、「Subpage」という番号も一緒にとるのだが、これで全体のどの部分のデータが取れているかがわかる。
具体的には、データは下記の図のようなイメージで2回とって1画面分のデータとなる。
ステータスレジスタの設定により2パターンの取れ方があるが、今回は上のパターン(横一行分のデータが1行飛ばしで取れるパターン)を使った。つまり、subPageが0のデータは奇数行目のデータ、subPageが1のデータが1のデータは偶数行目のデータとなっている。
温度データに変換
そのsubPageの値と、取れてきた1行飛ばしのデータは、生データを温度データに変換するメソッドMLX90640_CalculateTo()
の中で使っている。(中の計算ロジックは難しいのであまり見ず。)
変換した温度データを、最終的に配列に格納。(ここではTotalFrameData[]
)
この配列の中身が、サーマルカメラに映った画面(32*24)の1ピクセルごとの温度の値となる。
温度データからサーマル画像作成
温度データを画面に表示するための値にさらに変換する。
画面に温度を表す点を打つ方法は、こちらの以前の記事を参照。
privateasyncTask<BitmapImage>DoubleToRaindowColor(double[]totalFrameData)//temp:温度の値{intwidth=32;intheight=24;byte[]data=newbyte[width*height*4];for(inti=0;i<width;i++){for(intj=0;j<height;j++){// 指定の温度下限~上限の値を、0.0~1.0の値に変換するvarv=TemperatureTo0to1Double(totalFrameData[i+j*width]);// 0.0~1.0の値を、虹色を表すバイト列に変換するvarc=ColorScaleBCGYR(v);data[4*(i+j*width)]=c.Item4;// Bluedata[4*(i+j*width)+1]=c.Item3;// Greendata[4*(i+j*width)+2]=c.Item2;// Reddata[4*(i+j*width)+3]=c.Item1;// alpha}}// サーマル画像を作成WriteableBitmapbitmap=newWriteableBitmap(width,height);InMemoryRandomAccessStreaminMRAS=newInMemoryRandomAccessStream();BitmapEncoderencoder=awaitBitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId,inMRAS);encoder.SetPixelData(BitmapPixelFormat.Bgra8,BitmapAlphaMode.Ignore,(uint)bitmap.PixelWidth,(uint)bitmap.PixelHeight,96.0,96.0,data);awaitencoder.FlushAsync();BitmapImagebitmapImage=newBitmapImage();bitmapImage.SetSource(inMRAS);returnbitmapImage;}
上記の中の、指定の温度下限~上限の値を、0.0~1.0の値に変換するメソッドColorScaleBCGYR()
は、こちらのサイトを参考にさせていただいています。ありがとうございます。
完成
これで作成したbitmapを画面に表示したら、サーモグラフィの完成。
コード一式
ここまでで挙げてきた初期化やらデータ取得やらのメソッド以外にも、画面だったりC++のサンプルをもとに作ったEEPROM読み込み機能やらが多数ある。下記に一式置いているので参照ください。
https://github.com/tera1707/ThermalCamera
参考
公式ページ
https://shop.pimoroni.com/products/mlx90640-thermal-camera-breakout
データシート
https://cdn.sparkfun.com/assets/7/b/f/2/d/MLX90640-Datasheet-Melexis.pdf
値の大きさをサーモグラフィのような色に変換する
https://qiita.com/krsak/items/94fad1d3fffa997cb651
サンプルプログラム(arduino)
https://github.com/sparkfun/SparkFun_MLX90640_Arduino_Example/tree/master/Firmware
通信手順(どういうデータが取れるか、とか通信のお作法の参考になる)
https://github.com/sparkfun/SparkFun_MLX90640_Arduino_Example/blob/master/Firmware/Example1_BasicReadings/MLX90640_API.cpp
通信ドライバ(データの送り方の参考になる)
https://github.com/sparkfun/SparkFun_MLX90640_Arduino_Example/blob/master/Firmware/Example1_BasicReadings/MLX90640_I2C_Driver.cpp