スティックでサーボ制御する

# 目的
ジョイスティック1個で、サーボモーター2個を操作する。
※前回の記事「[PS4コントローラのスティックでarduinoを制御する](https://qiita.com/igapon1/items/e28ea499d85fa658c919)」のスティックからも、i2c接続で操作できるようにする。
<iframe width="560" height="315" src="https://www.youtube.com/embed/9XQHevjQ7eQ?si=dvW1bmIcalZAXkAU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# 背景
イベント向けに旗振りゲームを作ったので、作ったものをまとめておく。
旗揚げゲームとは、赤上げて、白上げないで・・・ってやつですねw
写真は、赤と白じゃなくて、母と父ですが、何でもいいと思いますw
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201344/8c86bcbc-89a4-ffb7-050b-4a4006b45ae7.png)

# 構成
- ベース電圧5Vのarduino x 1個
    - 今回はarduino MEGA 2560の互換品を使った
- USBケーブル x 1本
    - arduinoとPCを繋ぐ用途
- [Grove - Thumb Joystick](https://wiki.seeedstudio.com/Grove-Thumb_Joystick/) x 1個
    - スティックを動かして上限と下限を調べたところ以下の範囲だった
        - Min_Y：268、Max_Y：755
        - Min_X：272、Max_X：750
- [MG996R](https://akizukidenshi.com/goodsaffix/mg996r.pdf) x 2個
   - 操作電圧：4.8~ 6.6v
   - ストール電流：1400mA
- 旗振りゲームの旗x2振
- ミニブレッドボードx1個
- ブレッドボード用ピンx複数
- 47μf電解コンデンサx2個
- 電池ボックス4x1個
- 単三電池x4本

# コード説明
まず、サーボのサンプルコードは以下の[Sweep.ino](https://github.com/arduino-libraries/Servo/blob/master/examples/Sweep/Sweep.ino)を参考にする。
このサンプルでは、setup()で、9番PINをサーボの制御PINに割り当てて、
loop()のservo.write()で、サーボの角度を変更していることがわかる。
```
/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 https://www.arduino.cc/en/Tutorial/LibraryExamples/Sweep
*/

#include <Servo.h>

Servo myservo;  // create Servo object to control a servo
// twelve Servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the Servo object
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15 ms for the servo to reach the position
  }
  for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15 ms for the servo to reach the position
  }
}
```
修正後のスケッチは[Git](https://github.com/igaponr/NTigapon/blob/master/arduino/sketch/arduino_Joystick_ESC/arduino_Joystick_ESC.ino)から取得できる。
以下の通り、mode2を想定した、2本のスティック操作にしてある。
```
// A4(SDA),A5(SCL)は、I2C通信に使用する
// リモコンスティック操作モード2 mode2を例としてPINを以下の通りにする
Servo servo_throttle;  // D2PIN 左スティック上下 スロットル throttle
Servo servo_ladder;  // D3PIN 左スティック左右 ラダー ladder
Servo servo_elevator;  // D4PIN 右スティック上下 エレベーター elevator
Servo servo_aileron;  // D5PIN 右スティック左右 エルロン aileron
static char i2c_throttle = 'Z';  // i2cから受け取るスティックの値
static char i2c_ladder = 'Z';
static char i2c_elevator = 'Z';
static char i2c_aileron = 'Z';
```
setup()で、サーボにattachして、
Wireオブジェクトを使ってi2cのメッセージハンドラを登録して、
シリアル通信を開始する。
```
void setup(){
  // D0(RX),D1(TX)は、UART通信に使用する
  servo_throttle.attach(2, 1500 - 500, 1500 + 500);  // D2ピンを信号線として設定
  servo_ladder.attach(3, 1500 - 500, 1500 + 500);
  servo_elevator.attach(4, 1500 - 500, 1500 + 500);
  servo_aileron.attach(5, 1500 - 500, 1500 + 500);
  Wire.begin(I2C_CHANNEL);  // join i2c bus with address
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(115200);
  Serial.println("Start!");
}
```
loop()では、i2cから受信するまで、ジョイスティックの値に合わせて、サーボを動かす。
i2cから受信したら、それ以降はジョイスティックを無視する。
```
void loop(){
  if (i2c_throttle == 'Z' && i2c_ladder == 'Z' && i2c_elevator == 'Z' && i2c_aileron == 'Z'){
    int angle_throttle = map(analogRead(A0), MIN_SENSER_VAL, MAX_SENSER_VAL, MIN_SERVO_VAL, MAX_SERVO_VAL);  // センター515,最小269,最大754
    int angle_ladder = map(analogRead(A1), MIN_SENSER_VAL, MAX_SENSER_VAL, MIN_SERVO_VAL, MAX_SERVO_VAL);
    int angle_elevator = map(analogRead(A2), MIN_SENSER_VAL, MAX_SENSER_VAL, MIN_SERVO_VAL, MAX_SERVO_VAL);  // センター508,最小272,最大750
    int angle_aileron = map(analogRead(A3), MIN_SENSER_VAL, MAX_SENSER_VAL, MIN_SERVO_VAL, MAX_SERVO_VAL);
    angle_throttle = constrain(angle_throttle, MIN_SERVO_VAL, MAX_SERVO_VAL);
    angle_ladder = constrain(angle_ladder, MIN_SERVO_VAL, MAX_SERVO_VAL);
    angle_elevator = constrain(angle_elevator, MIN_SERVO_VAL, MAX_SERVO_VAL);
    angle_aileron = constrain(angle_aileron, MIN_SERVO_VAL, MAX_SERVO_VAL);
    servo_throttle.write(angle_throttle);
    servo_ladder.write(angle_ladder);
    servo_elevator.write(angle_elevator);
    servo_aileron.write(angle_aileron);
    Serial.print((char)map(angle_throttle, MIN_SERVO_VAL, MAX_SERVO_VAL, 'A', 'O'));
    Serial.print((char)map(angle_ladder, MIN_SERVO_VAL, MAX_SERVO_VAL, 'A', 'O'));
    Serial.print((char)map(angle_elevator, MIN_SERVO_VAL, MAX_SERVO_VAL, 'A', 'O'));
    Serial.print((char)map(angle_aileron, MIN_SERVO_VAL, MAX_SERVO_VAL, 'A', 'O'));
    Serial.print(F("\r\n"));
  }
}
```
i2cに受信があれば、受け取った値をシリアルに表示して、サーボを動かす。
```
void receiveEvent(int howMany){
  String str = "";
  while (Wire.available()) {
    char c = Wire.read();
    str += String(c);
    Serial.print(c);
  }
  Serial.print(F("\r\n"));
  i2c_ladder = str.charAt(0);  // l_x
  i2c_throttle = str.charAt(1);  // l_y
  i2c_aileron = str.charAt(2);  // r_x
  i2c_elevator = str.charAt(3);  // r_y
  servo_throttle.write(map(i2c_throttle, 'A', 'O', MIN_SERVO_VAL, MAX_SERVO_VAL));
  servo_ladder.write(map(i2c_ladder, 'A', 'O', MIN_SERVO_VAL, MAX_SERVO_VAL));
  servo_elevator.write(map(i2c_elevator, 'A', 'O', MIN_SERVO_VAL, MAX_SERVO_VAL));
  servo_aileron.write(map(i2c_aileron, 'A', 'O', MIN_SERVO_VAL, MAX_SERVO_VAL));
}
```

# 回路説明
回路図には表現されていないが、PCとarduino MEGA2560はUSBケーブルでつながっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201344/94e2f728-9928-e3fe-f0b6-05ddf7d634d6.png)
今は使っていないが、
もう一つジョイスティックがあれば、A1とA3に繋ぐとよい。
あと二つサーボがあれば、D2とD4に繋ぐとよい。
もしi2cがあれば、A4(SDA)とA5(SCL)に繋ぐとよい。(前回の記事「[PS4コントローラのスティックでarduinoを制御する](https://qiita.com/igapon1/items/e28ea499d85fa658c919)」のスティックからも、i2c接続できるはず)
ほとんど無負荷状態だが、サーボの電源はarduinoの電源とは別に用意しないと動かなかった。

# おわりに
スティックが一つで、旗を二つ操作するのは、とても分かりにくい。次回があればその時は、スティック二つで旗揚げゲームを作りたいw

# 関連リンク
- [arduino MEGA 2560 REV3　ピン配置](https://content.arduino.cc/assets/Pinout-Mega2560rev3_latest.pdf)
   - [servo ライブラリ](https://www.arduino.cc/reference/en/libraries/servo/)
- [Grove - Thumb Joystick](https://wiki.seeedstudio.com/Grove-Thumb_Joystick/)
- [MG996R](https://akizukidenshi.com/goodsaffix/mg996r.pdf)
## ドキュメント
- [Arduino日本語リファレンス](http://www.musashinodenpa.com/arduino/ref/index.php)