ソロでたのしむ

キャンプ、電子工作、DIY、乗り物などの趣味を紹介

トイラジコンを自動走行ロボットにしたい。超音波センサーとArduinoで改造

自動運転の車が実際に走る社会が近づいてきました。5千円ほどの材料を使っての簡単な工作レベルで、どこまで自動走行ができるのかを試してみます。

トイラジコンを自動走行ロボットにする。超音波センサー5個とArduinoで工作

自動運転(走行)で楽しくなる

自動運転と聞くと、スタート地点からゴール地点まで無人で走る車を思い浮かべます。そんな社会になったら移動時間の使い方の幅が多いに広がります。

もっと細かい分野では、自動ブレーキ、自動速度(全車との間隔)制御、自動駐車など、既に実用化されているものがあります。

さらに屋内では、お掃除ロボットが走りまわっている家も多くなっています。

楽になることはいいのですが、趣味の世界では楽しくないといけない!と思うので、今回自作する自動走行ロボットは、少しコミカルな動きを取り入れて、見ていて面白いものを目指します。

自動走行の動作

どのような動かし方をするかを決めます。

今回使うセンサーや認識ツールは、画像処理のような高価な技術は使わずに、一個あたり数百円で入手できる超音波センサーのみとします。

そのため、動作もシンプルで破綻(動けなくなる)しにくいものにします。

  • 障害物との距離が遠い方に操舵する
  • 前方障害物との距離に応じてスピードを変える
  • 行き詰まったら、少し後退して回転する

自動走行ロボットは、市販のトイラジコンを改造して作りますが、元のコントローラも有効利用します。

  • 2個のシーソースイッチを全後進の右旋回と左旋回ボタンとして、割り込み操作可能にする

ベースマシンの選定

ニッコー マックストラックス NIKKO MAXX TRAXX

改造ベースとするトイラジコンを選びます。

屋外で自動走行させる予定ですが、障害物の乗り越え性能はそこそこで良しとします。

少ないセンサーで、障害物の間に挟まったりして破綻しない為には、超信地旋回が有利です。そのため、タイヤで駆動し操舵するものではなく、モーターが2個になってしまいますが無限軌道タイプにします。

早速調べてみたところ数千円で入手できる無限軌道のトイラジコンをみつけました。

速度は可変せず、左右それぞれのモーターに対して前進-停止-後進の3パターンしかありません。

f:id:solocamptouring:20180602092718j:plain

常にフルパワーでは制御しにくいので、自動走行時は障害物との距離に応じて、Arduinoを使ってPWMで出力(速度)可変できるようにする計画です。

(補足)PWMについての簡単な説明

DCモーターの出力(回転数)は、モーター端子間の電圧を変えることでコントロールできます。

PWM(Pulse width modulation、パルス幅変調)は、電圧一定で、周期も一定のパルス波形にしたものです。これにより、電源電圧は一定でも、パルスの幅を変えれば電圧の平均値が変わるため、結果として電圧を変えた場合と同じ効果があります。

トイラジコンの分解調査

手元に届いて簡単に作動確認後、すぐに分解します。

電子回路基板

ニッコー マックストラックス(NIKKO MAXX TRAXX)の電子回路

トイラジコンに搭載されている基板についてネットで調べてみると、回路図が見つかりました。基板をラジコン用汎用モジュールとして取り扱っているようです。

写真中央のチップが、左のアンテナ(短いハーネス)から受け取った信号を処理する頭脳部です。

写真下の方にある、モーターからの配線が繋がっている2個のチップが、Hブリッジ回路が内蔵されたモータードライバICのようです。

この2個のチップにArduinoからパルスを入力すれば、PWM制御で速度コントロールができると思われます。

モーター

ニッコー マックストラックス(NIKKO MAXX TRAXX)の中身

直径約25mmほどのモーターが2個使われています。

左右駆動モジュール化されており、モーター単体レベルまで分解してはいないので、メーカーや型式は不明です。

制御しやすいステッピングモーターで同じサイズのものがあれば載せ替えたかったのですが、簡単に載せ替えられそうなものは無く、またギヤボックスへの取り付けやギヤの入手に苦労しそうな為、このままDCモーターを使ってPWMで速度制御してみます。

Arduino と超音波センサー5個の配線

超音波センサーモジュールについては、別の記事でスケッチ(プログラム)の使い方や得られる距離データを確認しました。

自動走行ロボットには、前向きに3個と後ろ向きに2個の、合計5個のセンサーを配置して、それぞれの正面にある障害物との距離を測ります。

複数の超音波センサーで距離を測る場合、通常は音の干渉による誤測定を防止するために1個づつ順番に音を出して測定していきます。

今回は、5個のセンサーは全て違う方向を向いているので、5個同時に音を出してみます。

Arduino を使う回路図

トイラジコンを自動走行ロボットにするArduinoスケッチ

回路図はFritzingで作成しています。

回路図右下のICが、トイラジコンのモータードライバー2個をまとめて現したものです。

このICに、 Arduino からパルスを入力する事で、モーターの出力を可変制御します。

  • D5 : 右側モーター後進用PWMパルス出力
  • D6 : 右側モーター直進用PWMパルス出力
  • D9 : 左側モーター直進用PWMパルス出力
  • D11: 左側モーター後進用PWMパルス出力

超音波センサーは、上の3個でロボットの前側、左の2個で後ろ側の距離を測定します。

  • D2 : 超音波センサー5個共通のトリガー出力
  • D7 : 前右側の超音波センサーのエコー入力
  • D8 : 前中央の超音波センサーのエコー入力
  • D10: 前左側の超音波センサーのエコー入力
  • D3 : 後ろ右側の超音波センサーのエコー入力
  • D4 : 後ろ左側の超音波センサーのエコー入力

トイラジコンに付属のリモコンからの全後進x2チャンネルの信号を、ラジコン頭脳チップから入力します。

  • A0(D14): リモコンの右側後進命令信号の入力
  • A1(D15): リモコンの右側前進命令信号の入力
  • A2(D16): リモコンの左側前進命令信号の入力
  • A3(D17): リモコンの左側後進命令信号の入力

自動走行ロボットの雰囲気を出すために、LEDを光らせます。

1個は超音波測定時に点灯して、状態をモニターできるようにします。

さらに1個を、前方の近距離に障害物があることを検知している場合に点灯します

  • A4(D18): 超音波測定中を表示するLEDへの出力
  • A5(D19): 障害物検知を表示するLEDへの出力

(補足)デジタルピンが足りない場合にアナログピンを使う方法

上の回路図説明で、リモコン操作による前後進のデジタル信号をアナログピンに入力しています。またLEDを光らせるデジタル信号のHIGH(5V)をアナログピンから出力しています。

私がよく使うArduino unoやnanoは入出力ピンの数が限られています。そこで、アナログピンをデジタルピンとして扱えるという便利な機能を使います。

アナログピン0~5には、14から19までのデジタルピン番号がついているので、たとえばアナログピン0をデジタルピン14の入力に使うことを宣言して、その状態(HighかLow)を読みます。

   pinMode(14, INPUT);
   digitalRead(14);

INPUTをOUTPUTに変えて、デジタル出力として使うこともできます。

   pinMode(18, OUTPUT);
   digitalWright(18, HIGH);

スケッチ(プログラム)全文は、最後に紹介いたします。

ロボット作りの作業開始

センサー部をつくる

超音波センサーを5個を使います。

f:id:solocamptouring:20180602172552j:plain

今回の作品はArduinoのピンをほぼ使い切っているだけあって、はんだ付け作業がかなり多いです。

最初に、合計5個のセンサーを、前向き用3個、後ろ向き用2個に分けて、それぞれ汎用基板にはんだ付けして配線します。

できたものがこちらです。

トイラジコンを自動走行ロボットにするための超音波センサー5個

それぞれの角度に固定されると、一段とセンサーらしさが増した気がします。

トイラジコンには、樹脂のホルダーやステーを使って装着します。前側は、センサー5個にオマケでついてきたホルダーを使いました。

トイラジコンの前側に超音波センサー3個を装着。自動走行ロボット化計画

後ろ側のセンサーは、余っていたL字押し出し材を使って、トイラジコンに固定します。使いまわしているので穴だらけですが、性能には影響しないので、このまま使います。

トイラジコンの後ろ側に超音波センサー2個を装着。自動走行ロボット化計画

トイラジコンの基盤に割り込み

ArduinoからのPWM用パルスの入力信号と、リモコン操作信号をArduinoに送る出力信号は、トイラジコンの基盤回路に割り込みます。

まずは、基盤のモーター制御用の信号線を削って切断します。

f:id:solocamptouring:20180603190820j:plain

写真の右側が信号を出すIC、左側がモータードライバーです。

IC側は、Arduinoに送る信号線をはんだ付けできるように、少しプリント導線を露出させておきます。

モータードライバー側は、丸く導線露出した部分がはんだ付けにちょうどいいので、使わせていただきす。

f:id:solocamptouring:20180603191412j:plain

写真下部の工場出荷時のはんだ付けが少し雑ですが、上部の自分で作業した部分も同レベルなので、そのままにしました。

追加したはんだ付け部は、あとでテープを使い絶縁保護しました。

Arduinoを搭載

配線本数が多いので、とにかく黙々とはんだ付けを続けます。

完成したらArduinoを搭載した基板はボックスに入れたいので、とりあえずトイラジコンにプロジェクトボックスの蓋を貼り付けて、さらにその上にArduinoを両面テープで貼り付けました。

Arduinoをトイラジコンに装着して自動走行ロボット完成

むき出し感がありますが、完成です!

自動走行 By アルドゥイーノ

早速、動かしてみます。家の中だとモーター音が響いて家族に驚かれるので、外で試験走行しました。

PWMでスピードを落としていますが、それでも速すぎて距離検知が遅れぎみです。

検知が追い付いつかないので暴れん坊のような動きになってしまい、自動走行というよりもランダム走行に見えます。

youtu.be

後退時に後ろの障害物に当たりにいってしまうのも、改善点です。

初回のスケッチ(プログラム)にしては動いている方だと前向きにとらえて、今後も自動走行に見えるレベルまで、プログラムや定数を改善していきます。

低速モーター化(後日談)

その後、少し間が開いてしまいましたが、低速走行を可能にするために、ステップモーターに組み替えました!

いい感じの速度とトルクになっています。

 

www.solocamptouring.com

 

トイラジコンを超音波センサー5個とArduinoで工作自動走行ロボットにしてみます

スケッチ(プログラム)の紹介

現時点での暴れん坊バージョンのスケッチを紹介いたします。

// 全てのセンサーを1発のトリガーで動かす
int trigPin = 2;
// 前3個のセンサーのエコー
int echoFr = 7; // front right
int echoFc = 8; // front center
int echoFl = 10; // front left
// 後ろ2個のセンサーのエコー
int echoRr = 3; // rear right
int echoRl = 4; // rear left

// PWMパルス出力
int pwmRf = 6; // right 前進
int pwmRr = 5; // right 後進
int pwmLf = 9; // left 前進
int pwmLr = 11; // left 後進

// リモコンの割り込み入力
int remoteRf = 15; // right 前進
int remoteRr = 14; // right 後進
int remoteLf = 16; // left 前進
int remoteLr = 17; // left 後進

// LED出力
int measurLed = 18; // 測定中に光るLED
int detectLed = 19; // 障害物検知中に光るLED

// モーター出力を定義
int maxSpeed = 100; // 最大出力を255以下で定義
int midSpeed = 90; // 中間出力を255以下で定義
int lowSpeed = 80; // 最小」出力を255以下で定義

// その他
boolean inReverse =false; // 後退中は真
int distSteer = 50; // 方向を変える横方向距離
int distStop = 50; // 危険と判断して止める距離
int distClear = 60; // 危険が無くなったと判断する距離
int maxStraight = 300; // 全速を許可する時の前方安全範囲
int nMeasure = 1; // 平均値をとるための計測回数

void setup() {

pinMode(trigPin, OUTPUT); 
pinMode(pwmRf, OUTPUT); 
pinMode(pwmRr, OUTPUT); 
pinMode(pwmLf, OUTPUT); 
pinMode(pwmLr, OUTPUT); 

pinMode(echoFr, INPUT);
pinMode(echoFc, INPUT);
pinMode(echoFl, INPUT);
pinMode(echoRr, INPUT);
pinMode(echoRl, INPUT);
pinMode(remoteRf, INPUT);
pinMode(remoteRr, INPUT);
pinMode(remoteLf, INPUT);
pinMode(remoteLr, INPUT);

Serial.begin(9600);

digitalWrite(measurLed, LOW); 
digitalWrite(detectLed, LOW); 
delay(2000);
}

void loop() {

if(digitalRead(remoteRf) == digitalRead(remoteRr) && digitalRead(remoteLf) == digitalRead(remoteLr)) 
{
  //リモコンの割り込み操作がなければ距離測定を開始

digitalWrite(measurLed, LOW); 

 if(inReverse == false){
 //前進
 int distanceFr;
 int distanceFc;
 int distanceFl;
 
 long durationFr;
 long durationFc;
 long durationFl;
 
 int distAFr = 0;
 int distAFc = 0;
 int distAFl = 0;

 int distFr ;
 int distFc ;
 int distFl ;

 int i ;
    for (i=0 ; i < nMeasure ; i++) {
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFr = pulseIn(echoFr, HIGH);// echoピンから帰ってくる数値mSを読む
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFc = pulseIn(echoFc, HIGH);
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFl = pulseIn(echoFl, HIGH);
    
    digitalWrite(measurLed, HIGH);
         
// 距離計算
    if(durationFr == 0){
    distanceFr = 250;
    } else {
    distanceFr= durationFr*0.034/2;
    }
    if(durationFc == 0){
    distanceFc = 250;
    } else {  
    distanceFc= durationFc*0.034/2;
    }
    if(durationFl == 0){
    distanceFl = 250;
    } else {    
    distanceFl= durationFl*0.034/2;
    }
 
    distAFr  = distAFr + distanceFr ;
    distAFc  = distAFc + distanceFc ;
    distAFl  = distAFl + distanceFl ;
    } 
    
  distFr = distAFr/nMeasure  ;   // 平均値をアウトプット
  distFc = distAFc/nMeasure  ; 
  distFl = distAFl/nMeasure  ;

 Serial.print(distFr);
 Serial.print("   ");
 Serial.print(distFc);
 Serial.print("   ");
 Serial.print(distFl);
 Serial.println("DRIVE");
 
digitalWrite(measurLed, LOW); 

  if( distFc < distStop){
  inReverse = true ; //前方に進めなければ後進
  } else {
     digitalWrite(detectLed, LOW); 
    if(distFr > distSteer && distFl > distSteer){ //左右が開けていて
      if(distFc > maxStraight){ //前方も開けていたら全開
        analogWrite(pwmRf,maxSpeed);
        analogWrite(pwmRr,0); 
        analogWrite(pwmLf,maxSpeed);
        analogWrite(pwmLr,0); 
      } else { //前方は開けていなければ中速
        analogWrite(pwmRf,midSpeed);
        analogWrite(pwmRr,0); 
        analogWrite(pwmLf,midSpeed);
        analogWrite(pwmLr,0); 
      }
    }else if(distFc > distClear){   //左右が開けていないが、前に充分スペース有
      if(distFr > distFl){
      analogWrite(pwmRf,lowSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,midSpeed);
      analogWrite(pwmLr,0); 
      }else if(distFr < distFl){
      analogWrite(pwmRf,midSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0); 
      }else{
      analogWrite(pwmRf,midSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,midSpeed);
      analogWrite(pwmLr,0);
      }
   }else{    //左右が開けておらず、前に充分なスペース無し 
      digitalWrite(detectLed, HIGH); 
      if(distFr > distFl){
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0); 
      }else if(distFr < distFl){
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,lowSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,0); 
      }else{
      analogWrite(pwmRf,lowSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0);
      }
   }
  }
 } else {
 //後退

    digitalWrite(measurLed, HIGH);   
 int distanceRr;
 int distanceFc;
 int distanceRl;
 
 long durationRr;
 long durationFc;
 long durationRl;
 
 int distARr = 0;
 int distAFc = 0;
 int distARl = 0;

 int distRr ;
 int distFc ;
 int distRl ; 

 int i ;

    for (i=0 ; i < nMeasure ; i++) {
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationRr = pulseIn(echoRr, HIGH);// echoピンから帰ってくる数値mSを読む
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFc = pulseIn(echoFc, HIGH);
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationRl = pulseIn(echoRl, HIGH);

// 距離計算

    if(durationFc == 0){
    distanceFc = 250;
    } else {  
    distanceFc= durationFc*0.034/2;
    }
    if(durationRr == 0){
    distanceRr = 250;
    } else {   
    distanceRr= durationRr*0.034/2;
    }
    if(durationRl == 0){
    distanceRl = 250;
    } else {      
    distanceRl= durationRl*0.034/2;
    }
    
    distAFc  = distAFc + distanceFc ;
    distARr  = distARr + distanceRr ; 
    distARl  = distARl + distanceRl ;
    } 
    
  distFc = distAFc/nMeasure  ;  
  distRr = distARr/nMeasure  ; 
  distRl = distARl/nMeasure  ;

 Serial.print(distFc);
 Serial.print("   ");
 Serial.print(distRr);
 Serial.print("   ");
 Serial.println(distRl);

 Serial.println("REVERSE");

digitalWrite(measurLed, LOW); 
   if(distFc < distClear){
    if(distRr > distSteer && distRl > distSteer){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed); 
    }else if(distRr > distRl){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,lowSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed); 
    }else if(distRr < distRl){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,lowSpeed); 
    }else{
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed);
    }
  } else {
  digitalWrite(detectLed, LOW); 
  if(distRr > distRl){
     analogWrite(pwmRf,midSpeed);
     analogWrite(pwmRr,0); 
     analogWrite(pwmLf,0);
     analogWrite(pwmLr,midSpeed);
     delay(100);
       inReverse = false;
  }else{
     analogWrite(pwmRf,0);
     analogWrite(pwmRr,midSpeed); 
     analogWrite(pwmLf,midSpeed);
     analogWrite(pwmLr,0); 
     delay(100);
       inReverse = false;
   }
  }
 }
} else {
//リモコン操作の割り込みがある場合
  if(digitalRead(remoteRf) == HIGH){
    analogWrite(pwmRf,lowSpeed);
    analogWrite(pwmRr,0);
  } else if(digitalRead(remoteRr) == HIGH){
    analogWrite(pwmRf,0);
    analogWrite(pwmRr,lowSpeed);
  } else {
    analogWrite(pwmRf,0);
    analogWrite(pwmRr,0);
  }
  if(digitalRead(remoteLf) == HIGH){
    analogWrite(pwmLf,lowSpeed);
    analogWrite(pwmLr,0);
  } else if(digitalRead(remoteLr) == HIGH){
    analogWrite(pwmLf,0);
    analogWrite(pwmLr,lowSpeed);
  } else {
    analogWrite(pwmLf,0);
    analogWrite(pwmLr,0);
  }
 }
}