【Arduino】子供と一緒にラジコンを作った話

本投稿は  TECOTEC Advent Calendar 2023  の21日目の記事です。

こんにちは。テコテックの八木です。

「何か面白いものないかな〜、なければ作るぞ!」と日々生活している中で子供と一緒に作った “遊びの一品” を紹介してみようと思います。

Arduino の Bluetooth ラジコンです。操縦には PS3 のコントローラーを使います。

主な構成要素

  • タミヤのバギー工作基本セット(マブチ130モーター付き)
  • Arduino Uno
  • USB ホストシールド 2.0 for Arduino
  • マイクロサーボ SG90
  • DRV8835 使用 DCモータードライブキット
  • 単三乾電池
  • PS3 コントローラー
  • Bluetooth USBドングル

タミヤの躯体にマイコン入りでハード・ソフトともに質実剛健。尖った機能は今のところ無いが拡張性高し。むき出しで不細工だがそれも味w

ソフトウェアを自分で書いているので、プログラミングすれば MAX_ACCELERATION パラメーターなどいくらでも書き換え可能、子供の成長に合わせて色々と調整できます。

なぜ作ったか

以前よりラジコンは空間把握能力を鍛えると思っていて、屋外ではタミヤのものなどで遊ばせているのですが、車が自分に向かってくる時やバックの時の操作(ハンドル操作と逆方向に進む)を早く上達させるために、リビングでも遊ばせたいと思っていました。

リビングでとなるとタミヤはデカいし、京商のミニッツはスピードが出過ぎるし、それ以外の商品だと無段階操作ができない等でもう少し精度が欲しい感じがあり、「それなら一緒に作ろうか!」という流れです。

ハード組み立て

タミヤのバギー工作基本セットを普通に組み立て、モーターにノイズ対策を施した後、サーボとモータードライバーを取り付け。 クリップを捻じ曲げてハンダ付け(溶接代わり)し、サーボに固定して舵にしました。

次は適当にプラ棒に穴を開けつつ骨組みを作り、その上に Arduino と電池ボックスを固定。 電池はモーター駆動用に単三アルカリ 2本 で 3V , Arduino 駆動用に単三アルカリ 6本 で 9V です。(Arduino の Vin は 7〜12V 必要)

そして USB Host Shield を 5V で使うべく3箇所をハンダブリッジ(説明書参照)し、Arduino にドッキングして以下の通り配線します。

  • モーター駆動用の 3V 電池
    • 赤黒 → モータードライバーのモーター電源用 VN, GND
  • モーター
    • 赤黒 → モータードライバーの OUT1, OUT2
  • モータードライバー
    • VCC, GND → Arduino の +3.3V, GND
    • IN1, IN2 → Arduino の 5PIN, 6PIN
  • サーボ
    • 黃 → Arduino の 3PIN
    • 赤 → Arduino の +5V
    • 茶 → Arduino の GND
  • Arduino駆動用の9V電池
    • 赤黒 → Arduino の Vin, GND

なお、写真では追加のノイズ対策として、黄色のブレッドボードにてサーボの VCC, GND 間に積層セラミックコンデンサ0.1μF, 電解コンデンサ47μF をかませるなどしています。

ソフト開発とマイコンへの書き込み

プログラムは以下の様にしました。USB Host Shield の PS3BT.ino を参考に、DRV8835 データシートなどを参照して書いてます。動かしながら気持ちの良い動きになる様に調整しました。

#include <PS3BT.h>
#include <usbhub.h>

#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
#include <Servo.h>

// debug
//#define DEBUG_STEERING
//#define DEBUG_ACCELARATION

// pin
const uint8_t SERVO_PIN = 3;
const uint8_t MOTER_PIN_1 = 5;
const uint8_t MOTER_PIN_2 = 6;

// PS3 のハット, サーボ
const uint8_t X_NEUTRAL = 255 / 2;
const uint8_t X_NEUTRAL_ZONE = 40;
const uint8_t X_LOWER_THRESHOLD = X_NEUTRAL - X_NEUTRAL_ZONE;
const uint8_t X_UPPER_THRESHOLD = X_NEUTRAL + X_NEUTRAL_ZONE;

const uint8_t SERVO_ANGLE_RANGE = 50;
const uint8_t DEFAULT_SERVO_ANGLE = 90 + 3; // 中
const uint8_t MIN_SERVO_ANGLE = DEFAULT_SERVO_ANGLE - SERVO_ANGLE_RANGE; // 右
const uint8_t MAX_SERVO_ANGLE = DEFAULT_SERVO_ANGLE + SERVO_ANGLE_RANGE; // 左

// モーター
const uint8_t Y_NEUTRAL = 0;
const uint8_t Y_NEUTRAL_ZONE = 20;
const uint8_t MAX_ACCELARATION = 255;
//const uint8_t BACKWARD_MAX_ACCELARATION = 127;

USB Usb;
BTD Btd(&Usb);
PS3BT PS3(&Btd);
Servo servo;

uint8_t x = X_NEUTRAL;
float xRate = 0.0;
uint8_t servoAngle = DEFAULT_SERVO_ANGLE;

uint8_t y = Y_NEUTRAL;
//uint8_t acceleration;

void setup() {
  Serial.begin(9600);
#if !defined(__MIPSEL__)
  while (!Serial);
#endif
  if (Usb.Init() == -1) {
    Serial.print(F("OSC did not start\r\n"));
    while (1);
  }
  Serial.print(F("PS3 Bluetooth Library Started\r\n"));

  stopBreak();
  initServo();
}


void loop() {
  Usb.Task();

  if (PS3.PS3Connected) {
    steer();
    accelerate();
  }

  delay(10);
}

// ステアリング
void steer() {
  // 0-255
  x = PS3.getAnalogHat(LeftHatX);

  if (X_LOWER_THRESHOLD <= x && x <= X_UPPER_THRESHOLD) {
    servo.write(DEFAULT_SERVO_ANGLE);
  } else {
    // 左
    if (x < X_LOWER_THRESHOLD) {
      calcLowerXRate();

      // e.g. 0.483, 25*0.483=12.075, 90+12.075=102.075
      servoAngle = DEFAULT_SERVO_ANGLE + (SERVO_ANGLE_RANGE * xRate);
    }
    // 右
    else {
      calcUpperXRate();

      // e.g. 0.364, 25*0.364=9.1, 90-9.1=80.9
      servoAngle = DEFAULT_SERVO_ANGLE - (SERVO_ANGLE_RANGE * xRate);
    }

    printSteering();
    servo.write(servoAngle);
  }
}

void calcLowerXRate() {
  // e.g. x=60, 118-60=58, 58/118=0.483
  xRate = float(X_LOWER_THRESHOLD - x) / X_LOWER_THRESHOLD;
}

void calcUpperXRate() {
  // e.g. x=180, 180-137=43, 43/118=0.364
  xRate = float(x - X_UPPER_THRESHOLD) / X_LOWER_THRESHOLD;
}


// アクセル
void accelerate() {
  // ブレーキ
  if (PS3.getButtonPress(CROSS)) {
    stopBreak();
  } else {
    // 前
    y = PS3.getAnalogButton(R2);
    if (y > Y_NEUTRAL_ZONE) {
      printAccelaration();

      analogWrite(MOTER_PIN_1, 0);
      analogWrite(MOTER_PIN_2, y);
    }
    else {
      // 後
      y = PS3.getAnalogButton(L2);
      if (y > Y_NEUTRAL_ZONE) {
        printAccelaration();

        analogWrite(MOTER_PIN_1, y);
        analogWrite(MOTER_PIN_2, 0);
      }
      else {
        // 空転
        analogWrite(MOTER_PIN_1, 0);
        analogWrite(MOTER_PIN_2, 0);
      }
    }
  }
}

void stopBreak() {
  analogWrite(MOTER_PIN_1, MAX_ACCELARATION);
  analogWrite(MOTER_PIN_2, MAX_ACCELARATION);
}

void initServo() {
  Serial.print(F("Will Initialize Servo\r\n"));

  servo.attach(SERVO_PIN);

  // テスト動作
  // 左へ
  while (servoAngle < MAX_SERVO_ANGLE) {
    servo.write(servoAngle++);
    delay(10);
  }
  // 右へ
  while (servoAngle > MIN_SERVO_ANGLE) {
    servo.write(servoAngle--);
    delay(10);
  }
  // 中へ
  while (servoAngle <= DEFAULT_SERVO_ANGLE) {
    servo.write(servoAngle++);
    delay(10);
  }

  Serial.print(F("Servo Initialized\r\n"));
}

// debug
void printSteering() {
#ifdef DEBUG_STEERING
  Serial.print(xRate);
  Serial.print(F(", "));
  Serial.print(servoAngle);
  Serial.print(F("\r\n"));
#endif
}
void printAccelaration() {
#ifdef DEBUG_ACCELARATION
  Serial.print(y);
  Serial.print(F("\r\n"));
#endif
}

PS3 コントローラーを認識させる

以下手順、シリアルモニタを見ながらやると分かりやすいです。

  1. Arduino にプログラムを書き込む
  2. USB Host Shield にドングルを挿入し、ドングルが点滅したら抜く
  3. PS3 コントローラーを USB Host Shield に繋ぎ、30秒程度置いてから抜く
  4. もう一度ドングルを挿入し、PS3 コントローラーの PS ボタンを押し、コントローラーの LED1 が光ったら OK

完成

細かい操作にも正確に反応します。イメージ通りのオリジナルな一品となりました。子供も楽しんでくれて満足です。

コントローラーの操作方法

  • 左ハット左右 → ステアリング
  • R2 → 前進アクセル
  • ✗ → ブレーキ
  • L2 → 後進アクセル

拡張のアイデア

子供の成長に合わせて、↑を使ってぶつからない車にするなど考え中。他にも各種センサー物色中。楽しみが広がります。

今回は以上となります。ザッツ・オール!!

テコテックの採用活動について

テコテックでは新卒採用、中途採用共に積極的に募集をしています。
採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。
ご興味を持っていただけましたら是非ご覧ください。

tecotec.co.jp