Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8899

[C#/WindowsIoT] RaspberryPi3にI2Cサーマルカメラ(サーモグラフィ)をつなげて温度を画像化する

$
0
0

もくじ
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

■写真
image.png

使った機材

全体の流れ

  • 回路作成
  • サーマルカメラとのI2C通信実装
    • VisualStudio2019ソリューションの設定
    • I2C通信のデータ送受信の準備
    • 初期化処理
    • EEPROM読み出し
    • サーマル生データを取得
    • 生データから温度に変換
    • 温度データからサーマル画像作成

回路作成

下図のような回路をつくる。
image.png
実際の配線
image.png

サーマルカメラとのI2C通信実装

以前実験した9軸センサのI2Cをもとに、通信部分の実装を行う。

VisualStudio2019ソリューションの設定

9軸センサのI2Cの方で同じ作業をしているので、そちら参照。

I2C通信のデータ送受信の準備

データシートによると、このサーマルカメラは、2バイトを一単位としてデータのやり取りを行う。

9軸センサ(MPU-9150)の場合は、1バイト単位でデータをやり取りしていたので、そこが違う。
9軸センサの場合は、下記のようにしていた。

9軸センサの送受信例.cs
// 書き込み: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でアドレス指定、データ指定を行うようにした。

サーマルカメラの送受信例.cs
// 書き込み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通信の準備)

初期化処理時の通信.cs
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にしなかったことに特に理由なし。)
image.png

試したところ、リフレッシュレートの設定によって、下のデータ取得のところで出てくる「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番地。
image.png

※EEPROMの読み出し項目は非常に多数あるので、コードは、githubのこちらを参照。

サーマル(生)データを取得 ~ 生データを温度データに変換

まずは、I2Cでサーマルの生データを取得する。
生データは温度データではないので、EEPROMから読み出したパラメータを使って温度に変換が必要。

データ取得は、データシートの「Measurement Flow」の項の流れに沿った処理を行う。
image.png

生データ読み出し.cs
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画面分のデータとなる。
image.png

ステータスレジスタの設定により2パターンの取れ方があるが、今回は上のパターン(横一行分のデータが1行飛ばしで取れるパターン)を使った。つまり、subPageが0のデータは奇数行目のデータ、subPageが1のデータが1のデータは偶数行目のデータとなっている。

温度データに変換

そのsubPageの値と、取れてきた1行飛ばしのデータは、生データを温度データに変換するメソッドMLX90640_CalculateTo()の中で使っている。(中の計算ロジックは難しいのであまり見ず。)

変換した温度データを、最終的に配列に格納。(ここではTotalFrameData[])

この配列の中身が、サーマルカメラに映った画面(32*24)の1ピクセルごとの温度の値となる。

温度データからサーマル画像作成

温度データを画面に表示するための値にさらに変換する。
画面に温度を表す点を打つ方法は、こちらの以前の記事を参照。

温度の値を32*24のピクセルの描画データに変換する.cs
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を画面に表示したら、サーモグラフィの完成。
image.png

コード一式

ここまでで挙げてきた初期化やらデータ取得やらのメソッド以外にも、画面だったり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


Viewing all articles
Browse latest Browse all 8899

Trending Articles