ProjectSRのアイコン画像
ProjectSR 2021年01月24日作成 (2021年02月28日更新)
製作品 製作品 閲覧数 5625
ProjectSR 2021年01月24日作成 (2021年02月28日更新) 製作品 製作品 閲覧数 5625

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

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千石電商
  • ProjectSR さんが 2021/01/24 に 編集 をしました。 (メッセージ: 初版)
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/24 に 編集 をしました。
  • ProjectSR さんが 2021/01/25 に 編集 をしました。
  • ProjectSR さんが 2021/01/25 に 編集 をしました。
  • ProjectSR さんが 2021/02/28 に 編集 をしました。
  • Opening
    Husqvarnaのアイコン画像 Husqvarna 2022/12/15

    はじめまして。
    arduinoとzig beeの通信で右後にモータが回転しません。LEDで左の前後の回転ではしっかり点灯しましたが、右ではLEDが薄暗く点灯しました。5番ピンを抜くモータが回転します。勿論、モータドライバ、arduino,等々故障を考えて交換しましたが改善しませんでした。
    改善方法を教えてください。

    ProjectSRのアイコン画像 ProjectSR 2023/01/28

    コメントありがとうございます.
    (まさかコメント頂けるコンテンツになるとは思っていませんでした)
    プログラムの件,検証します.
    少々時間がかかるかもしれません.

    また,急ぎで利用されたい場合は,
    本稿以外にも,NUEMK-2の記事で,ジョイスティックのプログラムを
    記載していますので,こちらのプログラムを転用するのも
    一つありかもしれません.
    ひとまず,応急処置の様なご提案となり,申し訳ありません.

    Husqvarnaのアイコン画像 Husqvarna 2023/01/30

    解決しました。
    原因はモータドライバTA7291の結線でした。ピンクを5pin→10、茶を10→5
    が間違っていました。

    2 件の返信が折りたたまれています
ログインしてコメントを投稿する