[Arduino, Processing] Bluetoothで遠隔操作するラジコンを作ってみた

初めてArduinoやProcessingを用いてラジコンを作ってみました。ただプログラムを書くだけでなくアウトプットが実際に触ることができるものに出てくるのはweb系とは少し違った面白さや達成感がありました。

# 完成品
![IMG_1228.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675878/c24791eb-a6c9-7e97-d45e-4dde34b088ea.jpeg)


# ソースコード
https://github.com/ktsgmr817/radio_control

# 実装したこと
無線通信(Bluetooth)による以下の遠隔操作


- 前進、後進
- ステアリング
- アームによる障害物の把持

# 主に使用したもの
- DCモータ
- モータドライバ
- サーボモータ
- Arduino
- ブレッドボード
- Bluetoothモジュール

# 難しかったこと
## Bluetooth通信による遠隔操作
今回、遠隔操作を行うことが要件として掲げていたので、Arduinoと通信する方法としてBluetoothを用いたシリアル通信で遠隔操作を実現しました。
キーボード操作により遠隔操作を行うことを想定していたので、押されたキーをシリアル通信でArduino側へ送信するようにしました。

コマンドは以下の通りです。

![スクリーンショット 2022-02-15 1.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675878/8f42a0bd-71df-7c21-700b-8854d60fb5f0.png)

- w, s, x → 前進, 後進, 停止
- a, d, n → 前輪の左右回転, ニュートラル
- r, g → 掴む, 放す
- i, k → アームを上下に傾ける

以下processingのプログラムです。
キーボードを離した瞬間にキーをArduino側へ送信することで、キーボードを押している間何も送信されないようにしています。
キーボードが押された瞬間に実行される関数keyPressed関数も存在します。しかし、これを定義してkeyPressed関数の中でキーを送信するようにしてしまうと、少し押しただけでも数回キーが送信されてしまいます。これはすぐに離しても長押しされていると認識されてしまうことが原因だと考えられます。よってkeyReleased関数の中で以下のように書くことで、キーボードから手が離れたときにのみ送信するようにしています。

```C++:Processing
import processing.serial.*;
Serial myPort;
char command;
void setup()
{
  myPort = new Serial(this,"ポート番号",115200);
}

void draw()
{
}

void keyReleased() {
  myPort.write(key);
}
```

以下Arduinoのプログラムです。

```C++:Arduino
#include <Servo.h>
#include <SoftwareSerial.h>
Servo wheel_servo; //前輪
Servo grab_servo;  //アーム(掴む)
Servo tilt_servo;  //アーム(上下に傾ける)
const int Tx = 2;
const int Rx = 3;
SoftwareSerial bluetooth(Tx, Rx);

//ピンを定義
const int AIN1 = 9;
const int AIN2 = 10;
const int BIN1 = 8;
const int BIN2 = 7;
const int PWMA = 11;
const int PWMB = 6;
const int WheelPin = 13;
const int GrabPin = 5;
const int TiltPin = 4;

//コマンドキーの定義
const char AUTO_MODE = '!';
const char W = 'w'; //前進
const char S = 's'; //後進
const char X = 'x'; //ブレーキ
const char A = 'a'; //左にサーボモータ(前輪)を回転
const char D = 'd'; //右にサーボモータ(前輪)を回転
const char N = 'n'; //サーボモータ(前輪)の角度を0に戻す
const char GRAB = 'g'; //掴む
const char RELEASE = 'r'; //放す
const char UP = 'i'; //アームを上げる
const char DOWN = 'k'; //アームを下げる

char command;

//スピードの定義
const int SPEED_FORWARD = 100;
const int SPEED_BACK = 100;

//角度の定義
int wheel_angle = 90; //範囲は 0 ~ 180
int grab_angle = 0;
int release_angle = 180;
int up_angle = 180;
int down_angle = 0;

void setup() {
  bluetooth.begin(115200);
  wheel_servo.attach(WheelPin);
  wheel_servo.write(wheel_angle);
  grab_servo.attach(GrabPin);
  grab_servo.write(grab_angle);
  tilt_servo.attach(TiltPin);
  tilt_servo.write(down_angle);
  pinMode(AIN1,OUTPUT);
  pinMode(AIN2,OUTPUT);
  pinMode(BIN1,OUTPUT);
  pinMode(BIN2,OUTPUT);
  pinMode(PWMA,OUTPUT);
  pinMode(PWMB,OUTPUT);
}

void loop() {
  if (bluetooth.available() > 0) {
    command = bluetooth.read();
    bluetooth.flush();
    if (command == A) {
      // 左にサーボモータ(前輪)を回転
      rotate_left();
    } else if (command == D) {
      // 左にサーボモータ(前輪)を回転
      rotate_right();
    } else if (command == N) {
      // ニュートラル
      wheel_neutral();
    } else if (command == W) {
      // 前進
      forward(SPEED_FORWARD, SPEED_FORWARD);
    } else if (command == S) {
      // 後進
      back(SPEED_BACK, SPEED_BACK);
    } else if (command == X) {
      // ブレーキ
      brake();
    } else if (command == GRAB) {
      // 掴む
      grab();
    } else if (command == RELEASE) {
      // 放す
      release();
    } else if (command == UP) {
      // アームを上に傾ける
      raise();
    } else if (command == DOWN) {
      // アームを下に傾ける
      lower();
    }
  } 
}

//直進する
void forward(int right, int left) {
  digitalWrite(AIN1,LOW);
  digitalWrite(AIN2,HIGH);
  digitalWrite(BIN1,LOW);
  digitalWrite(BIN2,HIGH);
  analogWrite(PWMA,right);
  analogWrite(PWMB,left);
}

//バックする
void back(int right, int left) {
  digitalWrite(AIN1,HIGH);
  digitalWrite(AIN2,LOW);
  digitalWrite(BIN1,HIGH);
  digitalWrite(BIN2,LOW);
  analogWrite(PWMA,right);
  analogWrite(PWMB,left);
}

//ブレーキ
void brake() {
  digitalWrite(AIN1,LOW);
  digitalWrite(AIN2,LOW);
  digitalWrite(BIN1,LOW);
  digitalWrite(BIN2,LOW);
}

//前輪を左に回転
void rotate_left() {
  if (is_valid_rotate(wheel_angle)) {
    wheel_angle -= 10;
    wheel_servo.write(wheel_angle);
  }
}

//前輪を右に回転
void rotate_right() {
  if (is_valid_rotate(wheel_angle)) {
    wheel_angle += 10;
    wheel_servo.write(wheel_angle);
  }
}

//現在の前輪の角度が0~180であるかを判定
bool is_valid_rotate(int angle) {
  return (angle < 180 && angle > 0);
}

//前輪の角度を0に戻す
void wheel_neutral() {
  wheel_servo.write(0);
}

//掴む
void grab() {
  grab_servo.write(grab_angle);
}

//放す
void release() {
  grab_servo.write(release_angle);
}

//アームの角度を上昇させる
void raise() {
  tilt_servo.write(up_angle);
}

//アームの角度を下降させる
void lower() {
  tilt_servo.write(down_angle);
}
```

# 全体を振り返って
　振り返ると楽しかったなと思います。実際に目の前でものが動くことの面白さは初めて感じることができました。触ることができるものを自分の意志通りにはじめて動かせたときは大きな達成感を感じました。これはWEB開発でも似たように感じることがあるので、この達成感はロボット開発でも似ているなと感じました。
　また、WEB開発と違ってプログラムが合っていても、ハードウェアが原因で思うように動かないことがあったのは大変でした。動かすためには回路をうまく組めていることや接触が良好であることが求められるので、動かないときにどこに問題があるのかを探るのに苦労しました。

今後も以下の点でWEB開発に携わるほうが面白そうだなと思っていますが、ロボット開発を少しでも経験できてよかったです。

- ロボットや機械は1つ作っても**数人の人にしか使われないこともある**が、WEBのプロダクトは1つ作れば**何千何万人の人に使ってもらう**ことができる点
- ロボットや機械は世に出してしまったら**それを回収しない限りアップデートしにくい**が、WEBのプロダクトは**常にアップデートしやすい**点

ここまで読んで頂きありがとうございました！