ラズタンクに赤外線障害物センサー2個を追加して、障害物を回避しながら自動走行する
追加した部品(IR赤外線障害物回避センサー)
- (どんな機能)IR赤外線障害物回避センサー
- (どんな用途)障害物回避、モノを追う、黒/白ライン追跡
- (検出距離)2cm~30cm 検出距離は調整できる
- (検出角度)35°
- (入力電源)DC 3~5V
- (LED赤)電源ON
- (LED緑)障害物検出
- (出力)ポート「OUT」は5Vリレーを駆動可能
ラズタンク(ここまでの完成の姿)
センサーの感度調整
このセンサーは障害物までの距離を変更(調整)できます。曇りの日中で、障害物までの距離が最長27cmを検出できました。この検証では距離15cmで設定しています。
センサーを本体に設置する
ラズタンク本体にセンサーを設置する方法は自由に試してください。下図のようにセンサーセットを組み立てておくと便利かもしれません。プラスチックのステーにセンサーをネジとナットで固定します。
ケーブルはVCCとGNDをブレッドボードに接続するので「オスーメス」2本と信号線はGPIOに接続するので「メス-メス」1本をセットにします。
センサーの電源
センサーの電源(VCC)とGNDは、図のようにブレッドボードに配線を工夫して確保してください
障害物センサーのGPIO配線
・左側が24ピン、GPIO8
・右側が26ピン、GPIO7
に接続しています。
ダッシュボード
自動運転モードは[Switch]ノードのON/OFFで切り替える
Node-REDフロー
開発中のフローです。このパターンでは、センサーのON/OFFをダイレクトにモーターの正転・逆転につないでいます。
もし[自動運転]のスイッチがONだったら、このフローを実行します。OFFならフローを実行しません。
つまり[自動運転モード & 障害物センサーが何も検知せず]のときモーターを回転させます。
正転するにはモーター1に[1]をモーター2に[0]を入力します。
AND関数が1を出力しNOT関数が[1を0に変えます。0のときは1に変えます]。
このように、障害物を検知したら向きを変えます(左に障害物があれば右を向き、右はその逆です)。
両方のセンサーに障害物がなければ前に進みます。
フローは下記JSONをコピペできます。
[{"id":"1c453b72.44c6a5","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"a8f36cf4.e49e2","type":"ui_switch","z":"1c453b72.44c6a5","name":"","label":"Auto Drive 🛻","tooltip":"","group":"919a5620.d1a0e8","order":1,"width":"3","height":"1","passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":120,"y":160,"wires":[["925532a8.d62c5"]]},{"id":"868ed064.eb2c5","type":"rpi-gpio in","z":"1c453b72.44c6a5","name":"障害物センサー右","pin":"26","intype":"down","debounce":"25","read":true,"x":130,"y":240,"wires":[["e347bfee.f7d15"]]},{"id":"b07a873c.5dded8","type":"rpi-gpio in","z":"1c453b72.44c6a5","name":"障害物センサー左","pin":"24","intype":"down","debounce":"25","read":true,"x":130,"y":80,"wires":[["867f0bfb.ace648"]]},{"id":"1b6e2e5c.195672","type":"rpi-gpio out","z":"1c453b72.44c6a5","name":"左1(GPIO 27)","pin":"13","set":true,"level":"0","freq":"","out":"out","x":790,"y":80,"wires":[]},{"id":"3bff8d22.9ae402","type":"rpi-gpio out","z":"1c453b72.44c6a5","name":"左2(GPIO 17)","pin":"11","set":true,"level":"0","freq":"","out":"out","x":790,"y":120,"wires":[]},{"id":"3bf89e37.966172","type":"rpi-gpio out","z":"1c453b72.44c6a5","name":"右1(GPIO 21)","pin":"40","set":true,"level":"0","freq":"","out":"out","x":790,"y":240,"wires":[]},{"id":"401e1a80.f1f514","type":"rpi-gpio out","z":"1c453b72.44c6a5","name":"右2(GPIO 20)","pin":"38","set":true,"level":"0","freq":"","out":"out","x":790,"y":280,"wires":[]},{"id":"867f0bfb.ace648","type":"function","z":"1c453b72.44c6a5","name":"AND","func":"// 2つのメッセージを受け取るための変数を初期化\ncontext.payload1 = context.payload1 || null;\ncontext.payload2 = context.payload2 || null;\n\n// メッセージのトピックに基づいてpayloadを保存\nif (msg.topic === \"autodrive\") {\n context.payload1 = msg.payload;\n}\nif (msg.topic === \"pi/24\") {\n context.payload2 = msg.payload;\n}\n\n// 両方のpayloadが揃ったら掛け算を行う\nif (context.payload1 !== null && context.payload2 !== null) {\n msg.topic = \"motor\";\n msg.payload = context.payload1 * context.payload2;\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":80,"wires":[["1b6e2e5c.195672","b453738a.cf398"]]},{"id":"925532a8.d62c5","type":"function","z":"1c453b72.44c6a5","name":"autodrive:0,1","func":"msg.topic = \"autodrive\";\nif (msg.payload === true) {\n msg.payload = 1;\n} else {\n msg.payload = 0;\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":160,"wires":[["867f0bfb.ace648","e347bfee.f7d15","f16a95a8.7723a8","b453738a.cf398","910db38c.a19ae"]]},{"id":"e347bfee.f7d15","type":"function","z":"1c453b72.44c6a5","name":"AND","func":"// 2つのメッセージを受け取るための変数を初期化\ncontext.payload1 = context.payload1 || null;\ncontext.payload2 = context.payload2 || null;\n\n// メッセージのトピックに基づいてpayloadを保存\nif (msg.topic === \"autodrive\") {\n context.payload1 = msg.payload;\n}\nif (msg.topic === \"pi/26\") {\n context.payload2 = msg.payload;\n}\n\n// 両方のpayloadが揃ったら掛け算を行う\nif (context.payload1 !== null && context.payload2 !== null) {\n msg.topic = \"motor\";\n msg.payload = context.payload1 * context.payload2;\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":240,"wires":[["3bf89e37.966172","910db38c.a19ae"]]},{"id":"b453738a.cf398","type":"function","z":"1c453b72.44c6a5","name":"NOT","func":"// 2つのメッセージを受け取るための変数を初期化\ncontext.payload1 = context.payload1 || null;\ncontext.payload2 = context.payload2 || null;\n\n// メッセージのトピックに基づいてpayloadを保存\nif (msg.topic === \"autodrive\") {\n context.payload1 = msg.payload;\n}\nif (msg.topic === \"motor\") {\n context.payload2 = msg.payload;\n}\n\n// 両方のpayloadが揃ったら掛け算を行う\nif (context.payload1 !== null && context.payload2 !== null) {\n // センサーがONなら逆転、OFFなら正転する\n if (context.payload2 === 1) {\n msg.payload = 0;\n } else {\n msg.payload = 1;\n }\n // もし自動運転がOFFなら止める\n if (context.payload1 === 0) {\n msg.payload = 0;\n }\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":120,"wires":[["3bff8d22.9ae402"]]},{"id":"910db38c.a19ae","type":"function","z":"1c453b72.44c6a5","name":"NOT","func":"// 2つのメッセージを受け取るための変数を初期化\ncontext.payload1 = context.payload1 || null;\ncontext.payload2 = context.payload2 || null;\n\n// メッセージのトピックに基づいてpayloadを保存\nif (msg.topic === \"autodrive\") {\n context.payload1 = msg.payload;\n}\nif (msg.topic === \"motor\") {\n context.payload2 = msg.payload;\n}\n\n// 両方のpayloadが揃ったら掛け算を行う\nif (context.payload1 !== null && context.payload2 !== null) {\n // センサーがONなら逆転、OFFなら正転する\n if (context.payload2 === 1) {\n msg.payload = 0;\n } else {\n msg.payload = 1;\n }\n // もし自動運転がOFFなら止める\n if (context.payload1 === 0) {\n msg.payload = 0;\n }\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":280,"wires":[["401e1a80.f1f514"]]},{"id":"f16a95a8.7723a8","type":"rpi-gpio out","z":"1c453b72.44c6a5","name":"モニタ","pin":"23","set":"","level":"0","freq":"","out":"out","x":330,"y":200,"wires":[]},{"id":"919a5620.d1a0e8","type":"ui_group","name":"自動運転","tab":"1ca0e03a.98b22","order":7,"disp":true,"width":"6","collapse":false,"className":""},{"id":"1ca0e03a.98b22","type":"ui_tab","name":"ラズタンク十号","icon":"dashboard","disabled":false,"hidden":false}]
Node-RED右上のメニューバー(三本線)から「読み込み」を選ぶ
JSON形式のフローデータをコピー&ペーストして[読み込み]ボタンを押すとフローの各ノードが表示される
テスト走行してみた v1.1
ひょっとしてキチンとプログラミングしなきゃダメかも。
Node-REDフローを改造します v1.2
前回はセンサーの検知結果をダイレクトにモーターに繋ぎました。今回は JavaScript でプチコーディングします。
基本設計もする
Node-REDフロー
ファンクション関数だけでセンサー検知とモーター制御をする
ひとつのファンクション関数でモーターを4つ制御するように設定する
ファンクション関数の JavaScript コード
// 変数の定義
var msgL1 = { payload:0 };
var msgL2 = { payload:0 };
var msgR1 = { payload:0 };
var msgR2 = { payload:0 };
// もし左右ともに障害物を検知したら[回避先を探す]
// 回避先を探すが、今は一旦停止する
context.init = context.init || false;
context.L = context.L || 0;
context.R = context.R || 0;
// メッセージのトピックに基づいてpayloadを保存
if (msg.topic === "pi/24") {
context.L = msg.payload;
}
if (msg.topic === "pi/26") {
context.R = msg.payload;
}
// もし手動運転(自動運転はOFF)ならnullを返しモーターに指示しない
if (msg.topic == "autodrive") {
if (msg.payload == true) {
context.init = true;
} else {
context.init = false;
}
}
// なにもしない(起動直後の動作)
if (context.init == false) {
return [{payload:0},{payload:0},{payload:0},{payload:0}];
}
// センサーを元に進行報告を決める
// L, Rともに障害検知(0)ならSTOP
if (context.L == 0 && context.R == 0) {
msgL1.topic = "Stop";
msgL1.payload = 0 ;
msgL2.payload = 1 ;
msgR1.payload = 0 ;
msgR2.payload = 1 ;
}
// L検知(0), Rなし(1)なら Right
if (context.L == 0 && context.R == 1) {
msgL1.topic = "Right";
msgL1.payload = 0 ;
msgL2.payload = 1 ;
msgR1.payload = 1 ;
msgR2.payload = 0 ;
}
// Lなし(1), R検知(0)なら Left
if (context.L == 1 && context.R == 0) {
msgL1.topic = "Left";
msgL1.payload = 1 ;
msgL2.payload = 0 ;
msgR1.payload = 0 ;
msgR2.payload = 1 ;
}
// LRともになし(1)なら Forward
if (context.L == 1 && context.R == 1) {
msgL1.topic = "Forward";
msgL1.payload = 1 ;
msgL2.payload = 0 ;
msgR1.payload = 1 ;
msgR2.payload = 0 ;
}
return [msgL1, msgL2, msgR1, msgR2];
テスト走行してみた v1.2
前回はロジック回路風ですが、今回はコードで運転。