ProjectSRのアイコン画像

Arduino + XBee によるジョイスティックワイヤレス操作システム

ProjectSR 2021年01月24日に作成  (2021年02月28日に更新)

Arduino + XBee によるジョイスティックワイヤレス操作システム

🔳知れる事

◇Aruino同士でSerial通信
 →(可変抵抗からの数値を送受信)

◇ワイヤレスで,複数のモータを制御
 →パケット設定して,送信側・受信側で通信を行う

◇ジョイスティックを操作しやすくする
→不感帯の設定

🔳概要・基本構成

①送信側のArduinoがオペレータのジョイスティックの操作量を読み取ります.

②操作量に応じたモータの回転速度を計算します.

③計算後は,受信側のArduinoへ数値を送ります.

④受信側のArduinoは数値を受け取って,モータへ回転指令を送ります.

🔳解説

基本構成は,

①送信側のArduinoが,オペレータのジョイスティックなどの操作量を読み取って,
モータの回転速度に関する数値を計算する.

②計算した後は,スレーブ側のArduinoへ数値を送って,モータの速度を調整しつつロボットが動く.

解説動画

ユーチューブ ニコニコ動画 にて 解説もしています.
ロボットがどのような動作をされるか見たい方は,ご参照ください.

ここに動画が表示されます

ニコニコ動画

Arduino XBeeジョイスティックワイヤレスコントローラシステム製作・解説

構成

基本構成は,

①送信側のArduinoが,オペレータのジョイスティックなどの操作量を読み取って,
 モータの回転速度に関する数値を計算する.
②計算した後は,スレーブ側のArduinoへ数値を送って,モータの速度を調整しつつロボットが動く.

ポイント

●オペレータに不快感なく,モータを制御するため,送信側のArduinoが操作量を読み取る事
●複数のモータの回転速度に関係する複数の数値を送受信する事
です.

ではArduinoが操作量を読み取るには
どうすればよいのか?
疑問に思われるかもしれませんが,よくプレステのコントローラに
搭載されているジョイスティック(アナログスティック)がありますよね?

実は,アナログスティックは電子部品で販売されています.
アナログスティックには,可変抵抗という電子部品が搭載されています.
⇓図のように,縦方向・横方向それぞれに操作したときの操作量を読み取るため,可変抵抗と呼ばれる電子部品が2つあります.
キャプションを入力できます

可変抵抗とは,文字通り,抵抗の値を変えることができる電子部品です.こここの製作例では,Arduinoに可変抵抗にかかる電圧を読み取らせます.
キャプションを入力できます

1⃣可変抵抗値読み取り→A/D変換→map関数変換

送信側Arduinoが電圧を読み取った後,
Arduino内のA/Dコンバータで値を変換し,操作量としてそのまま受信側Arduinoへ送ることも出来ます

受信側Arduinoは,analogwriteを使い,PWM信号出力を行って,モータの回転速度を調整するほかに,
回転方向の制御も行うため,負担が増え,通信に支障が出る可能性があります.
そのため,送信側Arduinoで予め,A/Dコンバータで変換した値から 更に map関数という関数を使って,
数値を変換し,受信側Arduinoへ送信しています.

まず可変抵抗の読み取り方とArduinoで値の送受信を抑えて行きます.
練習目的で,可変抵抗からの電圧値取得→A/D変換→map関数変換→Arduino送受信の一連の流れを,
簡単な回路とプログラムを組んでみました.

ArduinoのA0(黄色線),GND(黒線),5V(赤線)と可変抵抗を接続して,電圧を読み取ります.
送信側ArduinoのTXと受信側ArduinoのRXを接続(緑線)し,受信側ArduinoとPCを接続すれば回路は完成です.
(送信側Arduinoにも給電は必要です.USB,DCジャック付き電池ボックスで給電しましょう.)
キャプションを入力できます

□Program送信

const int analogPin = A0; int inputValue = 0; int outputValue = 0; void setup() { Serial.begin(9600); } void loop() { inputValue = analogRead(analogPin); outputValue = map(inputValue,0,1023,0,255); Serial.write(outputValue); delay(100); }

□Program受信

int incomingByte = 0; // 受信データ用 void setup() { Serial.begin(9600); // 9600bpsでシリアルポートを開く } void loop() { if (Serial.available() > 0) { // 受信したデータが存在する incomingByte = Serial.read(); // 受信データを読み込む if(incomingByte >= 0){ Serial.println(incomingByte); delay(100); } } }

2⃣ジョイスティックワイヤレス通信+複数モータ制御

この記事のメインテーマですね.
1⃣の方法では,可変抵抗1個の操作量を変換して送受信していましたが,
目的は,複数モータの回転方向と回転速度を制御する事なので,1⃣の処理だけでは足りません.
なので,以下の処理を行えるプログラムを組む必要があります.
①配列の準備と送受信
②パケット設定
③ジョイスティックの不感帯設定
順に説明します.

①配列の設定

配列を用意して,モータの回転方向と回転速度の数値を格納します.
キャプションを入力できます

ですが,送信中にノイズが入ると,回転方向tの値が変動して,受信側Arduinoは
回転速度の値なのか判別ができなくなります.このため,モータの指令が適切に行えず,
モータは暴走する可能性があります.
キャプションを入力できます

そこで,モータの回転方向・速度の数値前後に送受信の開始と終了を知らせる目印となる
数値を用意します.つまり,データ開始+モータ関連数値+データ終了
このデータ1塊のデータを送受信します.開始の数値は255,これを2回
その後モータ関連の数値を送受信して,最後に0を送受信し,次のデータ処理を実施します.

②パケット通信

キャプションを入力できます

こうすることで,ノイズの影響を受けても.受信側のArduinoは判断を誤る可能性は低くなります.
この「データ開始+モータ関連数値+データ終了」1塊のデータの送受信を行い,処理を行う方法を
パケット通信といいます.
また,今回のモータ回転速度の最大値は254としていて,「データ開始」の数値と重ならないようにしています.

③ジョイスティックの不感帯設定

ジョイスティックが中立位置にいると,可変抵抗をArduinoで読み取ると,
map関数で0~255の間で変換を行った場合,125の値になります.
送信側Arduinoでこの値を中心に操作領域を設けて,操作量を算出して受信側に送信します.

ジョイスティックの垂直位置の値をVDout
垂直位置の値をHDoutとして,モータ回転速度を計算
する式は,各値が125と比較して場合分けを行います
VDout>125の場合 (VDout - 125)×2
VDout=<125の場合 (125-VDout)×2
HDout>125の場合 (HDout -125)×2
HDout =< 125の場合 (125-HDout)×2
キャプションを入力できます

ですが,例えばオペレータがジョイスティックに指を添えるつもりでスティックに触れ,
中立位置から少しでも動かすと,その分受信側は動作指令が届いたと判断して
モータ回転指令が出ます.

そこで,左図のように,不感帯という,ある程度スティックが動いても,
モータ回転指令が出ないように,ジョイスティックの領域を設けます.
今回は垂直 水平にそれぞれ140,110を不感帯領域としています(左図の赤枠の部分)
キャプションを入力できます

スティックが不感帯領域にいる場合は,
送信側Arduinoでは,モータの回転方向の配列に2を格納して受信側へ送信.
受信側は2を受信したら,モータの回転速度は0にするようにプログラムを作成しております.
キャプションを入力できます

超信地旋回操作領域(緑枠)にスティックがある場合は,モータ回転方向は互い違いになるように,
送信側,受信側Arduinoでは1・0 or 0・1の組み合わせで処理するようにプログラムをしております.
キャプションを入力できます

制御フローチャート

送信側,受信側の制御フローチャートを記載します.

送信側

キャプションを入力できます

受信側

キャプションを入力できます

電子回路

送信側,受信側の電子回路を記載します.
Arduinoと電池ボックスの間にはDCジャックを半田付け
モータドライバーとDCモータ,電池ボックス間にはArduino用基板を用いたうえで,端子を半田付けして
接続しています
###送信側
キャプションを入力できます

受信側

キャプションを入力できます

プログラム

送信側

int SendBuf[4];
int i;
//const int analogPin(0) = A0;
//const int analogPin(1) = A1;

 int HDatain,VDatain,HDataout,VDataout;
uint8_t DirR;
uint8_t DirL;
uint8_t Duty,Duty1,Duty2;

 void setup() {
// put your setup code here, to run once:
Serial.begin(9600); 
}

 

void loop() {
// put your main code here, to run repeatedly:
VDatain = analogRead(A0);
VDataout = map(VDatain,0,1023,0,254);
HDatain = analogRead(A1);
HDataout = map(HDatain,0,1023,0,254);
//前進後退

 if(VDataout>140){
   if(HDataout>140){
    DirL = 0;
    DirR = 0;    
    Duty1 =VDataout;
    Duty2 =HDataout;
   }

      else if(140>=HDataout && 110<HDataout){
        DirL = 0;
        DirR = 0;    
        Duty1 =VDataout;
        Duty2 =VDataout;
        }

      else{
        DirL = 0;
        DirR = 0;    
        Duty1 =(120-HDataout)*2;
        Duty2 =VDataout;
      }             
    }

    else if(VDataout<=140 && VDataout>=125){

      if(HDataout>140){
       DirL = 1;
       DirR = 0;    
       Duty1 =HDataout;
       Duty2 =HDataout;
       }

      else if(HDataout<110){
        DirL = 0;
        DirR = 1;    
        Duty1 =(120-HDataout)*2;
        Duty2 =(120-HDataout)*2;
        }

      else{
        DirL = 2;
        DirR = 2;    
        Duty1 =0;
        Duty2 =0
      }                      
    }

    else if(125>VDataout && VDataout>=110)
   {
      if(HDataout>140){
        DirL = 1;
        DirR = 0;    
        Duty1 =(HDataout-140)*2;
        Duty2 =(HDataout-140)*2;
    }

      else if(HDataout<110){
        DirL = 0;
        DirR = 1;    
        Duty1 =(110-HDataout)*2;
        Duty2 =(110-HDataout)*2;
      }

      else{
        DirL = 2;
        DirR = 2;    
        Duty1 =0;
        Duty2 =0;
      }                      
    }

    else{
      if(HDataout>140){
        DirL = 1;
        DirR = 1;    
        Duty1 =(120-HDataout)*2;
        Duty2 =(120-VDataout)*2;
      }

      else if(HDataout<140 && 110<HDataout){
        DirL = 1;
        DirR = 1;    
        Duty1 =(110-VDataout)*2;
        Duty2 =(110-VDataout)*2;
      }

      else{
        DirL = 1;
        DirR = 1;    
        Duty1 =(110-VDataout)*2;
        Duty2 =(110-VDataout)*2;
      }                      
    }

   //SendBuf[0] = 0xFF;
   //SendBuf[1] = 0xFF;
   SendBuf[0] = DirL;
   SendBuf[1] = Duty1;
   SendBuf[2] = DirR;
   SendBuf[3] = Duty2;
   //SendBuf[4] = 0;
   Serial.write(255);
   Serial.write(255);
   //Serial.print(255); 
   //Serial.print(255); 
   for(i=0;i<4;i++){
   //Serial.print(SendBuf[i]);
   Serial.write(SendBuf[i]);
   }

   //Serial.print(DirL); 
   //Serial.print(DirR); 
   //Serial.print(HDataout); 
   //Serial.println(VDataout);    
   Serial.write(0);
  delay(100); 
}

受信側

#define PIN_IN1  5
#define PIN_IN2  6
#define PIN_IN3  2
#define PIN_IN4  3
#define PIN_VREF1 9 
#define PIN_VREF2 10 
 
int RcvData=0;//
int RcvBuf[4];
int MT_Rin=5;
int MT_Lin=6;
int MT_RoutSerial=0;
int MT_LoutSerial=0;
int i=0;
int RcvState=0;
//int RcvCountF=0;
//int RcvCountS=0;
int RcvC=0;
int MT1D=0;
int MT2D=0;
int MT1S=0;
int MT2S=0;
int RcvCount=0;

void setup() {
  Serial.begin(9600); // 9600bpsでシリアルポートを開く
  pinMode(PIN_IN1,OUTPUT); 
  pinMode(PIN_IN2,OUTPUT); 
  pinMode(PIN_IN3,OUTPUT); 
  pinMode(PIN_IN4,OUTPUT); 
}

 

void loop() {
  while(Serial.available() > 0){
    RcvData=Serial.read();
    //Serial.println(RcvData);
    //Serial.println(RcvState);               
          switch(RcvState){
            case 0:
                 if(RcvData==255){
                    RcvState = 1; 
                    //Serial.print("RcvData1"); 
                    //Serial.println(RcvState);             
                 }

                break;
            case 1:
                 if(RcvData==255){
                    RcvState = 2;
                    //Serial.print(" RcvData2"); 
                    //Serial.println(RcvData);             
                 }

                 else{
                    RcvState = 0;            
                 }                               
                 break;

            case 2:
                RcvBuf[RcvCount]=RcvData;
                RcvCount++;
                if(RcvCount >= 4){
                 RcvCount=0;     
                 RcvState = 3; 
                 //Serial.print(" RcvData*"); 
                 //Serial.println(RcvData);  
                }
                break;              

            case 3:
                MotorCnt();
                RcvState=0;
                break;                            
              }                               
           }
         }

void MotorCnt(){
  //モータ1の制御
  //digitalWrite(13, HIGH );
  //delay(100); 
  //digitalWrite(13, LOW );
  //delay(100);
  if(RcvBuf[0]==0 || RcvBuf[0]==1){
    if(RcvBuf[0] == 0){
      MT_LoutSerial = RcvBuf[1];
      //Serial.print("左前");
      //Serial.print(MT_LoutSerial);    
      analogWrite(PIN_VREF1,MT_LoutSerial); 
      digitalWrite(PIN_IN1,HIGH);
      digitalWrite(PIN_IN2,LOW);
      }

      else{
      MT_LoutSerial = RcvBuf[1];
      //Serial.print("左後");
      //Serial.print(MT_LoutSerial);    
      analogWrite(PIN_VREF1,MT_LoutSerial); 
      digitalWrite(PIN_IN1,LOW);
      digitalWrite(PIN_IN2,HIGH);
      }

    if(RcvBuf[2] == 0){
    MT_RoutSerial = RcvBuf[3];
    //Serial.print("右前");
    //Serial.println(MT_RoutSerial);
    analogWrite(PIN_VREF2,MT_RoutSerial); 
    digitalWrite(PIN_IN3,LOW);
    digitalWrite(PIN_IN4,HIGH);
    }

    else{
    MT_RoutSerial = RcvBuf[3];
    //Serial.print("右後");
    //Serial.println(MT_RoutSerial);
    analogWrite(PIN_VREF2,MT_RoutSerial); 
    digitalWrite(PIN_IN3,HIGH);
    digitalWrite(PIN_IN4,LOW);
    }
  }

  else{
    //Serial.println("停止");
    digitalWrite(PIN_IN1,LOW);
    digitalWrite(PIN_IN2,LOW);  
    digitalWrite(PIN_IN3,LOW);
    digitalWrite(PIN_IN4,LOW);        
    }
  delay(100);
}

電子部品

最後に本製作例で使用した電子部品を紹介します.

部品名 購入先
Arduino uno 秋月電子通商orマルツパーツor千石電商
TA7291P amazon(ただし販売終了なので中古)代替品でTB6643KQ
タミヤダブルギアボックス 秋月電子通商orマルツパーツor千石電商
電線 秋月電子通商orマルツパーツor千石電商or近くのホームセンター
Arduino変換基盤 秋月電子通商orマルツパーツor千石電商
XBeeと変換基盤 秋月電子通商orマルツパーツor千石電商
ジョイスティックレバー 秋月電子通商orマルツパーツor千石電商
ピンヘッダとピンソケット 秋月電子通商orマルツパーツor千石電商
電子基板 秋月電子通商orマルツパーツor千石電商
ログインしてコメントを投稿する