[11/25 ログファイルの問題はSDカードのエラーだったため修復 完成]
[11/24 ほぼ完成も、ログファイルが更新されない重大なバグが残る]
[9/28 自動撮影完了]
[9/15 GalileoでのUSBカメラ撮影完了(準備実験4)]
[8/17 Galileo 互換のLinux開発環境構築とGalileoでのUSBカメラ撮影について追記]
[8/10 水分センサデータ計測結果・V4L2によるカメラキャプチャについて追記]
■はじめに
今回は前回のレビューに引き続きGalileoを使った工作です。
前回はGalileo発売から間もない時期で右も左もわからない状況でしたが、経験値がある今回はスムーズにいけるか!?といったところです。前回内容を放置気味ですが、併せて進めていきたいと思ってはいます。。。
7/20 一次公開ではとりあえずコンセプトとできている検証項目を報告します。
■Galileoとは
前回レビューで得た結論としては、Arduino互換命令が使えるLinuxPCです。
GUI表示機能がないのでArduinoスケッチを含むコマンドラインのプログラムで操作するのが基本です。VNCなどでログインするとGUIも使えるそうですが・・・。
■今回作りたいもの
今回はいわゆる自動水やり器と温湿度測定、自動写真撮影により、自動的に植物栽培と観察記録をしてくれる装置をつくります。
システム全体図
GalileoはC、C++プログラムとArduinoスケッチの両機能が使えるので、以下のとおり分担させようと考えています。
C、C++プログラム
カメラのインターバル撮影
Arduinoスケッチ
水分センサを利用したポンプの制御と環境のインターバル測定。カメラと同期しません。
USBカメラでの撮影は初なので、締切は余裕をもって10月末にしました。
InterfaceにWebカメラを使っている作例はあったので、最悪はそれに沿っていきます。
■ハードウェア構成
・カメラ
カメラをGalileoにUSB接続します。
・各種センサ
前回はI2Cのシリアル通信でしたが、今回はアナログIO対応のセンサも使ってゆきます。一部GroveというArduinoのために規格化されたセンサ商品を使ってみます。
・ポンプ制御
電子工作は全くわからないのでGlove対応のリレーを使ってポンプのスイッチオンオフをやります。
モータ制御チップBD6211Fを搭載したモータ制御モジュールも買ってあります。
・ケース
前回通気性の悪い小さなケースにして温度問題が発生したので、今回は家に余っていた100円ショップのMDケースを使います。最近は高い外気温からスケッチが止まる事態が多発しているため、USB-ACアダプタの5V出力を使った電動ファンで空冷します。
・データ取得
前回同様無線LANコンバータMZK-RP150NAを使います。
・電源
電源はAnkerのUSB-ACアダプタです。電動ファンx2と本体、無線LANコンバータの4つに電源供給です。
■ハードウェアをみてみる
下記以外に無線LANコンバータを使いますが、前回と同様なので省略します。
0.Galileo本体
Galileo本体は前回と同じですが、今回は最初からヒートシンクを付けて運用です。
1.Groveベースシールド
コネクタが大量についています。対応するセンサや制御器をこのコネクタにつないで利用できます。
2.水分センサー
Groveベースシールドと同じコネクタが付いていて、接続ケーブルで簡単につなげます。サンプルスケッチも提供されています。
センサ部以外は防水が必要と書いてあったので余っていたお菓子のケースにコーキング材を塗りたくって保護しました。
3.リレー
これもGrove対応で、サンプルスケッチが提供されています。直流は10A、30Vまで対応しています。ポンプのオンオフをやります。
5.給油ポンプ
本来は灯油をくみ上げるものですが、今回は水くみに使います。プラスチックでできているので水でも問題ないと考えています。
6.温湿度センサ
HIH-6130です。これはGlove対応ではありません。I2Cを使うタイプです。取説を読まないで買ったのですが、ネット上にarduinoで使った例もあるので、まあ使えると思っています。
7.電動ファン&ケース
100円ショップのMDケースを万能はさみで切ってビニールテープでファンをつけています。非常に雑ですが、あとはファンにフィルターをつければ機能としては良いと思っています。
■準備実験
準備実験として機器の操作方法に習熟します。
1.水分センサーでの計測
水分センサーでの連続計測ができるか、どのくらいが水やりタイミングになるのかをみてみます。アナログ読み取りとSDカードへのデータ保存を行います。
まずは今年種をまいた唐辛子をつかいます。3日間計測したのですが、あまり水を吸わなかたので、別途計測は行います。とはいえ適切に冷やしてやれば、スケッチが止まらずに長期計測ができることは分かりました。
ファンが届く前だったのでUSB扇風機を無理やり当てています
計測結果のグラフは下記のとおりです。途中で1回配置を変えたので断続データです。
結果から、100以下ぐらいが水分なしの閾値と判断しました。
2.Linuxでカメラ撮影
GalileoはLinuxをつんでいるので、LinuxディストリビューションをインストールしたPCでカメラ撮影プログラムが動くことを確認します。OSはUbuntuを利用します。
[8/10]V4L2を使ったキャプチャ
GalileoのLinuxイメージではV4L2というリナックスのビデオツールが使えると書いてあります。
参考:https://learn.sparkfun.com/tutorials/galileo-getting-started-guide
V42Lのサンプルでlibv4lライブラリを利用して簡単にキャプチャできるものがあったので、テストしました。
結論として、UbuntuではUSBカメラ撮影はできましたが、libv4lを使うためにいくつかパッケージが必要だったので、galileo開発環境に付属するコンパイラでは同じサンプルをコンパイルできませんでした。ライブラリを入れればいいのでしょうが、やり方がわかりません。
以上の知見から、3節ではInterfaceの記事を参考に、Galileoと互換性のあるコードの作成を行います。
3.Galileo 互換のLinux開発環境構築
2でもいったとおりWebカメラを使った撮影に向けて、Galileoで動く実行ファイルを作ります。
現状のスキルではIntel GalileoのLinux実行ファイルの作成は以下の3つくらいが候補になります。
ア.Galileo用Arduino IDE付属のコンパイラを利用した実行ファイルの生成
イ.Galileo本体にビルドツール(GCCなど)が入ったLinuxを入れて、本体で実行ファイルを生成
ウ.Galileoと互換性のある環境で実行ファイルを生成
アは追加ライブラリの入れ方がわからない、イは同時にスケッチが動くのかよくわからない。ということでウを実施します。
3.1 OSの選択と仮想マシンの作成
今回はGalileoのCPU Intel Quarkと同じアーキテクチャを対象としたOSを利用して互換開発環境を構築します。
QuarkはSIMD演算(MMX)のないi586アーキテクチャです。このi586は初代Pentiumのもので、Pentium2以降はi686というアーキテクチャです。i486、i386もあり、下位互換はあるようです。
これをふまえて、i586以前のアーキテクチャを対象としたディストリビューションを選びます。
調べたところ、最近のLinuxディストリビューションではi686アーキテクチャがベースなので、メジャーなUbuntuの最新版などは使えません。isoイメージの名前にi386とあってもふたを開けたらi686というものばかりになっています。
雑誌InterfaceではCentOS 4.8を利用していましたが、古めかしい感じで自分には敷居が高いと感じました。
いろいろ調べた末、今回はパッケージ管理が初心者にも簡単なUbuntu9.04を利用しました。サポートは終了していますがインストールメディアはネット検索すれば出てくると思います。
マシンを用意するほどの重作業ではないのでVMwareを利用した仮想マシンを作成しました。
gccのバージョンをチェックすると、i486対象となっており、Galileoで使えそうです。
3.2 Ubuntu9.04のパッケージ管理
Ubuntuはapt-getコマンドで簡単にソフトをインストールできますが、9.04はサポート外のため、普通にやると404エラーが返ってきてしまいます。
ウェブ上の情報を頼りに、/etc/apt/sources.listを改変したところ、パッケージを取得できるようになりました。
3.3 コンパイルと実行のテスト
コンパイルのテストとして、以前のレビュー↓で利用した、配列同士の乗算プログラムを動かしてみます。
1000要素の配列同士を500000回掛け算するプログラムです。変更点としては乗算回数に加え、SIMD関係の部分はごっそり削除しています。
静的リンクをしないとGalileoが認識しないので(理由は知りません)オプションとして-staticを指定します。最適化オプションは最速になってくれるだろう-O3をつけます。
具体的なコマンドは下記のとおり
g++ -static -o bench.elf bench.cpp -O3
Galileoでの実行はWinSCPで実行ファイル転送後、実行可能なように権限(r,w,xなど)を変更、TeraTermでログインして実行します。
比較対象として、i5 2450M搭載のマシンのVMware上での実行結果を載せました。
Galileoは普通のパソコンのCPUの数十倍遅いということになるのでしょうか。
これでひとまず互換環境が整いました。
4.GalileoでUSBカメラ撮影
GalileoでCプログラムを動してのカメラ撮影テストです。
2節でも説明した通り、V4L2というライブラリがGalileoのLinuxイメージに組み込んであるそうなので、そのサンプルコードを使います。
カメラはLogicool C270です。安価な固定フォーカスWebカメラです。
・PC側でのプログラムビルド
2節、3節ともにサンプルコードのコンパイルにはlibv4l-devのパッケージが必要でした。詳細は忘れましたが、sudo apt-cache search v4lなどで必要そうなパッケージを検索、手あたり次第インストールしています。
ライブラリをそろえたあと、以下のコマンドでコンパイル、リンクします。
gcc testV.c -o v4lVM.elf -lv4l2 libv4lconvert.a -lpthread –static
静的リンクをすると、libv4lconvertが見つからないというようなエラーがでたので、ライブラリ”libv4lconvert.a”をコンパイルするディレクトリに置いています。
・Galileo側での実行
実行する前にカメラを認識してもらうようにGalileoのコマンドラインから以下のコマンドを打っておく必要があります。
modprobe uvcvideo
通常の実行ファイルと同様に実行します。テストに当たってファイル名を引数で指定できるように改変してあります。よく分からないエラーが出ていますが、とりあえず数十秒かかって終了しました。
GalileoへのリモートログインでCプログラムを実行
ファイルを確認してみると、確かに”out.ppm”というファイルができています(赤枠)。
Windowsに移してjpg変換したものがこれ↓です。GUIが無いパソコンで画像を撮影したことはなかったので新鮮です。
5.ポンプを操作
リレーを使ってポンプを操作します。リレーはデジタル出力をHIGHにするとオン、LOWにするとオフになるだけなので、割愛します。
6.温湿度測定テスト[11/24]
温湿度センサはI2CなのでWire.hを使って値を取得します。
サンプルプログラムが提供されているのですが、
一点だけ気をつけることとして、データ更新のときに”0”を書きこんでやる必要があります。
これをやらないと、値が更新されません。
図のとおりの変更です。
■自動撮影実験
定期的な撮影と保存を繰り返す実験をやってみます。インターバル撮影だけならあまっているスマホだけで良いので車輪の再発明感がすごいです。
タイマーが簡単なArduinoスケッチを使って予備実験で作ったプログラムを20分ごとに1回叩いて撮影するようにしました。Galileoならではのやり方です。
撮影画像のサムネイルが次の画像です。月日をファイル名に入れるのを忘れたので、年時間の順のファイル名になっています。。。
朝顔を撮影しましたが、なんか非常にみにくいですね。
画像を大きくしたいと思って960x720にしたのですが、明るさ調整が効いてない気がします。
ソースコードは記事末尾に記載します。
■自動水やり実験[11/24]
冬越しを狙っている唐辛子とパキラを対象に実験をやりました。
水分センサ550を閾値にしたところ、唐辛子は下の写真のような水量になりました。
■システム統合実験(完成)
各要素は検証できたので、完成形をつくりました。
外部はこんな感じです。
内部はこんな感じで、余裕をもって収納しています。
以下の仕様でプログラムを作成しました。
水分センサ測定割り込み周期:1s
撮影割り込み周期:1200s
水分ありの閾値:計測値550
水分なしの閾値:計測値100
→全部つなげたらデータが5分程度しか記録されなくなったので、直します。
Galileoにログインしてみたところ、Read-Onlyになっていました。SDカードのファイルエラーだったので、WindowsのCHKDSKで修復して改善しました。
撮影結果速報が下図です。一回水をやっているのでほぼ変化なしです。ホースを貼り付けているテープがわずかずつ剥がれているようです。
時系列は下のような感じです。途中でヒゲがでているのは、水分センサポジションを差し替えたからです。
途中でGalileoの温度が急に上がっていますが、ファンレスに切り替えたためです。冬だから大丈夫だろうという判断ですが、ちょっと不安かも知れませんね。
■総括と反省
・感想
ソフト面では今回のレビューもGalileoの機能をうまく使っていく方針でLinuxでのUSB機器操作とArduinoでの制御をうまく組み込めたと思います。ウェブカメラのキャプチャは思ったよりスムーズでしたが、Quark CPUにあわせたコンパイルで苦労しました。
ハード面では前回寸法限界のケースを使って熱対策で苦労したので、大きなファンとケースを用意したことで、非常に安定しました。ただし、ファンの消費電力を考えるとパソコンでもいいかなというレベルです。
また、Galileo本体の電源がUSBで取れるのは相変わらず便利です。Gen2が出ましたが、USB駆動ができるGen1もまだまだ使い道はあると感じます。
・反省
まずソフトウェアの反省としては同期していない写真撮影にはマルチスレッドを使うべきだったということがあります。現在はおなじタイマで割り込みをしているので、写真撮影によって数分の空白が生まれているようです。
ハードとしては、きちんとモータドライバを理解して、リレーなど使わないほうが良かったのではないかということがあります。
あと最大の反省は時期が遅れに遅れて、撮影に値する成長が記録できなかったこと。せっかくレビュー機会をいただいたのに最大に生かせませんでした。
とはいえ相当安定したシステムになったので、今後いろいろな植物で試したいと思います。Galileoのレビューと離れるので、日記などかもしれませんが。
■付録
・自動撮影実験のソースコード
#include <TimerOne.h>
#include <sys/time.h>
#include <SD.h>
#include <LiquidCrystal.h>
#include<Wire.h>
//キャプチャ用変数
char timestr[9];
char str1[]="./v4lVM.elf media/mmcblk0p1/";
char name[100];
//センサログ用変数
File datafile; //ログファイル
int nsample;//現在のサンプリング数
char temp_raw[6];//温度文字列
int temp;
long count=0;//データカウント 割り込みごとにリセット
double v0cum,v1cum,i0cum,i1cum,p0cum,p1cum;//積算電圧、電流、電力;
double v0ave,v1ave,i0ave,i1ave,p0ave,p1ave;//平均電圧、電流、電力
int sensorPin = A0; // select the input pin for the potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor
#define NELEMS(arg) (sizeof(arg) / sizeof((arg)[0]));
void setup() {
system("modprobe uvcvideo");
// put your setup code here, to run once:
pinMode(13, OUTPUT);
//シリアルポート開
Serial.begin(9600);
//IPアドレス取得
// system("/etc/init.d/networking restart");
//SDカードセットアップ
Serial.println("Initializing SD card...");
Serial.println(SD.begin());
if (!SD.begin(4)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
Timer1.initialize(1000); //タイマーの時間刻み設定 1ms
Timer1.attachInterrupt( timerIsr,1000000 ); // 水分センサの割り込みの時間刻み設定 1000ms
Timer1.attachInterrupt( timerIsr2,120000000 ); //画像キャプチャの割り込みの時間刻み設定 1200s
}
void loop() {
// put your main code here, to run repeatedly:
//Serial.println(v1ave);//デバッグ用シリアル出力
//Serial.println(i1ave);
// Serial.println(p1ave);
delay(10);
// Serial.println(count);
}
//
void timerIsr()
{//水分センサとプロセッサ温度のデータ取得
char buf[128];
time_t now;
struct tm t1;
now = time(NULL);
gmtime_r(&now, &t1);
sensorValue = analogRead(sensorPin);
temp = getQuarkTemp();
sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d,%d,%d\n",
t1.tm_year+1900, t1.tm_mon+1, t1.tm_mday,t1.tm_hour, t1.tm_min, t1.tm_sec,sensorValue,temp);
//Serial.print(buf);
if(!SD.exists("datalog.txt")){
//Serial.println("datalog.txt does not exist, creating.");
system("touch /media/mmcblk0p1/datalog.txt");
}
datafile=SD.open("datalog.txt",FILE_WRITE);
if(datafile){
Serial.println(datafile.size());
datafile.print(buf);
datafile.close();
}
//LCD操作
//lcd.setCursor(0, 0);//今後考える
// lcd.print(buf);
// LED点滅
digitalWrite( 13, digitalRead( 13 ) ^ 1 );
}
void timerIsr2()
{
//カメラキャプチャ
system("date +%Y%H%M >tmp.txt");
FILE *fp;
fp = fopen("tmp.txt", "r");
fgets(timestr, 9, fp);
Serial.println(timestr);
fclose(fp);
strcpy(name,str1);
strcat(name,timestr);
Serial.println(name);
system(name);
// LED点滅
digitalWrite( 13, digitalRead( 13 ) ^ 1 );
}
int getQuarkTemp(){
FILE *fp;
fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
fgets(temp_raw, 5, fp);
fclose(fp);
int temp = atoi(temp_raw);
temp /= 100;
return temp;
}
ZIGSOWにログインするとコメントやこのアイテムを持っているユーザー全員に質問できます。