RAPIRO(ラピロ)にArduinoシールドを装備させる
誤解を招きそうなタイトルですが、かといって嘘を言っているワケでもなく。
世間一般で言うところのArduinoシールドを利用できるようにしたのではなく、Arduino自体を(ガンダムが持ってそうな)物理的なシールドとしてラピロに装備させました。前回、Arduino経由でラピロのRaspberry Piにセンサ群を接続しましたが、いろいろビローンとなってしまっている状態(?)だったので、こういう形でまとめ上げてみました。
ベースになっているのは、
Arduino Fioと、
壽屋の『フリースタイル・シールド』です。
Arduino Fioを使っている理由は、前回も述べましたが、電源供給の自由度が高いのと、後々無線通信とかやりたくなったときに役に立ちそうだからです。
壽屋のシールドは、自分にカッコよろしいシールドを3Dモデリングする技術が未だないので、それを補うために導入しました。
というわけで、まずはこんな簡単な補助具を3Dプリンタで作成してみました。
色を塗って、こんな感じに。
左右の小さい穴に、壽屋のプラシールドの突起を差し込みます。で、中央の4つの穴には、強力なネオジム磁石をセットします。
これはシールドの部品を探しに行ったときに偶然見つけたものです。世の中便利なものが安く売っているもんですね。
そして、ラピロの腕も一回解体して、中にネオジム磁石をボンドでくっつけます。
これで、シールドが簡単に着脱できるようになります。もちろん、両面テープで直接貼っつけてしまってもいいのですが、何かこっちの方がカッコ良さげなので、こういう形にしました。ただそれだけの理由です。ハードポイントがあると、色々換装できそうで何かワクワクしますよね。ガンダムF90みたいで。しませんか。そうですか。
3Dプリンタで作った補助具にシールドの部品を差し込んで、両面テープでArduino Fioを貼っつけて、シールド裏側に仕込んだセンサと配線します。左上から反時計周りに、湿度センサ、温度センサ、照度センサ、マイクです。うまいことはまってくれるもんです。
一番設置に難儀したのが、人感(動き)センサです。これは人の在/不在の検出に使えるので是非とも使いたいセンサなのですが、図体がデカい上に、シールドの裏側に配置してしまうと測定範囲が狭くなってしまいます。
色々悩んだ挙句、最終的に、こんな感じでソケットを伸ばしてきて、真正面にドーンと据えるようにしました。
こんなんでも、間にXBeeをセットして、さらにシールドの余りパーツをかぶせてみると、意外といい感じにはまってくれました。
こんな感じに簡単に分解もできるので、
Arduinoへのソフト書き込みも簡単にできます。
あとはこのArduinoと、ラピロの頭の中のRaspbery PiをI2Cで接続してやる必要があります。
だいぶゴチャゴチャしていますが、I2Cの線は、ラピロの後頭部のUSB口の隙間から後ろに伸ばして、外のArduinoと接続します。また、Arduinoへの電源供給は、ラピロの後頭部から外に露出している、Raspberry PiのUSB口からUSBケーブルで行います。
…え、それならわざわざI2C用の線を用意せずに、データ通信もUSBのシリアル通信を使えば良かったんじゃないかって?…まあ、それは好みの問題ということで。個人的に、Raspberry Piの中にArduino IDEの環境を用意する、というのが、どうしてもしっくりこなかったので。
さて、とにもかくにも、これで準備完了。前に作成した、irMagicianを使った赤外線リモコン銃と一緒に装備させまして。
ラピロ 大地に立つ!
シールドはネオジム磁石で固定されているので、落下はしません。ただ、強力なネオジム磁石も捻りには弱いので、あんまり腕を激しくバタバタさせると落ちます。それでも、普通に歩かせたりする分には、落下することはありませんでした。
Arudino FioとUSBの接続口が上の方に来ていたので、ケーブルが腕の動きに干渉することを心配していたのですが、最終的には全く干渉しませんでした。よかったよかった。
後ろから見た図。USBケーブルに、I2Cの通信ケーブルを巻きつけています。最初はさらに、以下のスパイラルケーブルを巻きつけて、見栄えを良くしようとしていました。
が、いざやってみると、ケーブルの柔らかさがなくなってしまい、シールドが外れやすくなってしまったので、結局外してしまいました。
そういえば偶然ですが、この後ろからケーブルが伸びて腕の武器に接続されている感じ、『仮面ライダードライブ』の魔進チェイサーの武器に似ている感じがします。
赤外線リモコンの銃も、ドライブのトレーラー砲みたいな感じですね。
これでセンサの値をRaspberry Piで取得できることを確認したのですが、実際やってみるとRaspberry PiとArduino間の通信に失敗したり、センサのエラー値を取得してしまうということもあったので、もう少し安定して値を取得できるよう、前回のコードを少し修正してみました。
まずはArduino側から。都度読み取った値を返すのではなく、直近1分間の平均値を返すようにしています。
#include <Wire.h> #define SLAVE_ADDRESS 0x21 const int HIGH_STATE = 1; const int LOW_STATE = 0; const float SUPPLY_VOLT = 3.3; int TEMP_PIN = A2; int HUMID_PIN = A0; int ILL_PIN = A3; int MIC_PIN = A1; int MOTION_PIN = 12; float temp_avg = 0.0; float humid_avg = 0.0; float ill_avg = 0.0; float mic_avg = 0.0; int motion_avg = 0; // すべてのセンサは、問い合わせ時点から過去1分間の出力値の平均値を返す。 // ただし、モーションセンサについては、平均値が0.2以上なら「人がいた」と判定して1、 // 0.2未満なら「人がいない」と判定して0を返す const int SENSOR_LOG_NUM = 60; float temp_log[SENSOR_LOG_NUM]; float humid_log[SENSOR_LOG_NUM]; float ill_log[SENSOR_LOG_NUM]; float mic_log[SENSOR_LOG_NUM]; float motion_log[SENSOR_LOG_NUM]; int loop_count = 0; byte sendByte; float getTemperature(){ int LM35DZ_Value = analogRead(TEMP_PIN); return ((SUPPLY_VOLT * LM35DZ_Value) / 1024) * 100; } void updateTemperatureAvg(){ float sum = 0.0; for(int i=0;i<SENSOR_LOG_NUM;i++){ sum += temp_log[i]; } temp_avg = sum/SENSOR_LOG_NUM; } float getHumidity(float temp){ int HIH4030_Value = analogRead(HUMID_PIN); float voltage = HIH4030_Value/1024.0 * SUPPLY_VOLT; float sensorRH = 161.0 * voltage / SUPPLY_VOLT - 35; float trueRH = sensorRH / (1.0546 - 0.00216 * temp); return trueRH; } void updateHumidityAvg(){ float sum = 0.0; for(int i=0;i<SENSOR_LOG_NUM;i++){ sum += humid_log[i]; } humid_avg = sum/SENSOR_LOG_NUM; } float getIlluminance(){ int AMS302_Value = analogRead(ILL_PIN); float lx = AMS302_Value*(3300.0/1024.0)/1000.0/(0.26/100); return lx; } void updateIlluminanceAvg(){ float sum = 0.0; for(int i=0;i<SENSOR_LOG_NUM;i++){ sum += ill_log[i]; } ill_avg = sum/SENSOR_LOG_NUM; } float getMic(){ float ADMP401_Value = analogRead(MIC_PIN); return 3.3*ADMP401_Value/1024.0; } void updateMicAvg(){ float sum = 0.0; for(int i=0;i<SENSOR_LOG_NUM;i++){ sum += mic_log[i]; } mic_avg = sum/SENSOR_LOG_NUM; } void updateMotionAvg(){ float sum = 0.0; int flag = 0; for(int i=0;i<SENSOR_LOG_NUM;i++){ sum += motion_log[i]; } float avg = sum/SENSOR_LOG_NUM; if(avg < 0.2){ flag = 0; }else{ flag = 1; } motion_avg = flag; } void receiveData(int byteCount){ int i2c_command = -1; while(Wire.available()){ i2c_command = Wire.read(); Serial.print("i2c data received: "); Serial.println(i2c_command); switch(i2c_command){ case 0xF0: updateTemperatureAvg(); sendByte = (uint8_t)(int(temp_avg*100)); break; case 0xF1: sendByte = (uint8_t)(int(temp_avg*100) >> 8); break; case 0xF2: updateHumidityAvg(); sendByte = (uint8_t)(int(humid_avg*100)); break; case 0xF3: sendByte = (uint8_t)(int(humid_avg*100) >> 8); break; case 0xF4: updateIlluminanceAvg(); sendByte = (uint8_t)(int(ill_avg)); break; case 0xF5: sendByte = (uint8_t)(int(ill_avg) >> 8); break; case 0xF6: updateMicAvg(); sendByte = (uint8_t)(int(mic_avg*100)); break; case 0xF7: sendByte = (uint8_t)(int(mic_avg*100) >> 8); break; case 0xF8: updateMotionAvg(); sendByte = (uint8_t)motion_avg; break; case 0xF9: sendByte = (uint8_t)(motion_avg >> 8); break; } } } void sendData(){ Wire.write(sendByte); } void setup() { // put your setup code here, to run once: Serial.begin(57600); // シリアル通信速度 pinMode(TEMP_PIN, INPUT); pinMode(HUMID_PIN, INPUT); pinMode(ILL_PIN, INPUT); pinMode(MIC_PIN, INPUT); pinMode(MOTION_PIN, INPUT); Wire.begin(SLAVE_ADDRESS); Wire.onReceive(receiveData); Wire.onRequest(sendData); for(int i=0;i<SENSOR_LOG_NUM;i++){ temp_log[i] = 0.0; humid_log[i] = 0.0; ill_log[i] = 0.0; mic_log[i] = 0.0; motion_log[i] = 0.0; } Serial.println("Ready."); } void loop() { if(loop_count == SENSOR_LOG_NUM){ loop_count = 0; } temp_log[loop_count] = getTemperature(); humid_log[loop_count] = getHumidity(temp_log[loop_count]); ill_log[loop_count] = getIlluminance(); mic_log[loop_count] = getMic(); motion_log[loop_count] = (float)digitalRead(MOTION_PIN); Serial.print(temp_log[loop_count]); Serial.print(" / "); Serial.print(humid_log[loop_count]); Serial.print(" / "); Serial.print(ill_log[loop_count]); Serial.print(" / "); Serial.print(motion_log[loop_count]); Serial.print(" / "); Serial.println(mic_log[loop_count]); loop_count++; delay(1000); }
マイクだけは、もうちょっと扱い方を考えた方がいいかもしれません。多分このままのソースでは、あまり役に立ちません。
続いて、Raspberry Pi側。Arduino側で平均値計算とかするようになったので、I2C通信の間隔を少し長めにとるようにしました。じゃないと、変な値が返ってくることが多かったので。bottleフレームワークを使って、URLアクセスでArduinoとの通信がスタートするようにしています。以下、抜粋ですが、importで余計なコードが入っているのはご了承ください。
# coding: utf-8 from bottle import route, request, response, run, hook, static_file import requests import serial import time import smbus bus = smbus.SMBus(1) ARDUINO_ADDRESS = 0x21 registerMap = { "temperature":[0xF0,0xF1], "humidity": [0xF2,0xF3], "illuminance":[0xF4,0xF5], "mic": [0xF6,0xF7], "motion": [0xF8,0xF9] } def read_value(sensor): time.sleep(0.01) bus.write_byte(ARDUINO_ADDRESS,registerMap[sensor][0]) time.sleep(0.01) data_1 = bus.read_byte(ARDUINO_ADDRESS) bus.write_byte(ARDUINO_ADDRESS,registerMap[sensor][1]) time.sleep(0.01) data_2 = bus.read_byte(ARDUINO_ADDRESS) #print data_1 #print data_2 data = ((data_2 << 8) | data_1) if sensor in ['temperature','humidity','mic']: data = data*1.0/100 return data @route('/v1/robots/rapiro/sensors') def control_arduino_read(): print read_value('temperature') print read_value('humidity') print read_value('illuminance') print read_value('mic') print read_value('motion') return "OK, Arduino READ."
さてさて、これでラピロは周りの状況を色々センシングできるようになりました。
この先の展開としては、取得したセンシング値をどう利用するか、というところになってくるかなーとぼんやりと思っているのですが。。。それとは別に、わざわざArduino Fioを使ったのだから、XBeeを使ってドアの開閉とかもセンシングできるようにしてみてはどうだろうか、という考えもあります。なんかファンネルっぽくてカッコ良いではないですか!
うーん、どっちに進むにしても、どちらもこれまでほとんどやったことがないから、結構時間をとられそう。。。手軽に、タッチセンサの追加とかをやってみても良いかもしれないけれど。ああ、やりたいことがあり過ぎる。早くゴールデンウィークにならんかな。
ディスカッション
コメント一覧
Pretty nice post. Thank you so much!
ピンバック & トラックバック一覧
[…] (3/21追記:こちらで修正版のソースを公開しています) […]
[…] RAPIRO(ラピロ)にArduinoシールドを装備させる […]
[…] 前からちょっとずつ左にずれながら歩いているなあとは思っていたのですが、左腕にArduinoシールドをつけたことにより、目をつぶるわけにはいかないぐらい左に曲がるようになってしま […]
[…] ちなみに、上のシールドの工作については、以前こちらに書いています。ビームサーベルはシールドのArduinoにつながっていて、遠隔地からXBeeでデータが送られてくると発光してお知ら […]