AUTO DRIVE (自動運転)

ラズタンクに赤外線障害物センサー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

前回はロジック回路風ですが、今回はコードで運転。


コメントを残す