Wii 리모컨(Wiimote)으로 RC Car 조종하기

이젠 벽돌이 되어버린 니텐도 Wii를 바라보다 갑자기 아깝다는 생각이 들어 폭풍 검색을 하다가 Wii 리모컨을 활용할 수 있는 괜찮은 소스를 찾은 것 같아 아래와 같이 소개 합니다.

잘~ 응용하면 재밋는 장난감이 되겠네요.

Circuit@Home에서 소개 받았습니다!

RC car controlled by Wii Remote on Arduino http://www.circuitsathome.com/mcu/rc-car-controlled-by-wii-remote-on-arduino

곳곳에서 Wii 리모컨을 사용한 재미있는 프로젝트를 볼 수 있습니다. PC에서 Bluetooth Stack를 이용한 프로젝트는 많은 있지만, PC를 사용하지 않고 마이컴 (AVR, PIC)에서 Bluetooth Stack를 이용한 프로젝트는 적은 것 같습니다. 그래서 (새삼 느낌입니다 만…) 마리오 카트를 조종하는 느낌으로 무선조종을 만들어 보았습니다. #바나나를 던지거나 하지는 않습니다.

하드웨어 (Hardware)

Arduino에서 Bluetooth Stack을 실현하여 무선조종서보와 ESC를 제어합니다.

송신기 (Transmitter)

  • Wii 리모컨 (Wiimote)

수신기 (Reciver)

  • Arduino
  • USB Host Shield
  • Bluetooth USB 어댑터 (Dongle)

통신 거리는 Bluetooth 특성상 대략 10m 정도 밖에 안될 거라고 예상합니다. 통신 거리를 늘리려면 XBee 등을 사용해야 할 것 입니다.

소프트웨어 (Software)

Arduino 용 라이브러리는 github 에 있습니다.
이 라이브러리는 Richard Ibbotson이 만든 코드 wiiblue.pde 을 기반으로하고 있습니다.
또한 이 라이브러리를 사용하기 위해서는, Oleg Mazurov가 개발 한 라이브러리 가 필요합니다.

샘플 스케치 (Sketch)

 

SterringWii.ino

#include "WiiRemote.h"
#include <MemoryFree.h>
#include <Servo.h>
#define PIN_STEERING_SIGNAL 2
#define PIN_ESC_SIGNAL  3
#define SERIAL_DEBUG 0
enum eAngle {
  STEERING_ANGLE_MAX    = 165,  // to right
  STEERING_ANGLE_MIN    = 15,   // to left
  STEERING_ANGLE_CENTER = 90,
  STEERING_ANGLE_STEP   = 5,
  THROTTLE_ANGLE_MAX    = 80,
  THROTTLE_ANGLE_MIN    = 10,
  THROTTLE_ANGLE_CENTER = 90,
};
enum eServoPulse {
  SERVO_PULSE_NEUTRAL = 1500, // Futaba compatible, 1.55msec
  SERVO_PULSE_MAX     = 1800, // to left
  SERVO_PULSE_MIN     = 1200, // to right
};
enum eESCPulse {
/*
 *  Futaba timing
 *
 *  0us     1072us         1522us          1922us
 *   +---------*------------+-*-+-------------*
 *   |   n/a   |   forwad   |d|d|   Reverse   |
 *   +---------*------------+-*-+-------------*
 *          Max Forwad     Neutral         Max Reverse
 *
 *   d: dead zone, +10us and -10us
 */
  ESC_PULSE_NEUTRAL = 1522,
  ESC_PULSE_BRAKE   = 1600,
  ESC_PULSE_FWD_MAX = 1200, // 1072
  ESC_PULSE_FWD_MIN = 1510,
  ESC_PULSE_FWD_3RD = (ESC_PULSE_FWD_MIN - 200),
  ESC_PULSE_FWD_2ND = (ESC_PULSE_FWD_MIN - 140),
  ESC_PULSE_FWD_1ST = (ESC_PULSE_FWD_MIN - 80),
  ESC_PULSE_REV_FIX = 1650,
  ESC_PULSE_REV_MAX = 1700, // 1922
  ESC_PULSE_REV_MIN = 1600,
};
enum eGear {
  GEAR_1ST = 1,
  GEAR_2ND = 2,
  GEAR_3RD = 3,
};
WiiRemote wiiremote;
Servo SteeringServo;
Servo ESC;
void setup()
{
#if SERIAL_DEBUG
  Serial.begin(9600);
  Serial.print("\r\nfreeMemory() reports: ");
  Serial.print(freeMemory(), DEC);
#endif
  SteeringServo.attach(PIN_STEERING_SIGNAL);
  SteeringServo.writeMicroseconds(SERVO_PULSE_NEUTRAL);
  ESC.attach(PIN_ESC_SIGNAL);
  ESC.writeMicroseconds(ESC_PULSE_NEUTRAL);
  wiiremote.init();
  /*
  unsigned char wiiremote_bdaddr[6] = {0x00, 0x1e, 0x35, 0xda, 0x48, 0xbc};
  wiiremote.setBDAddress(wiiremote_bdaddr, 6);
  wiiremote.setBDAddressMode(BD_ADDR_FIXED);
  */
}
void loop()
{
  wiiremote.task(&myapp);
}
int steering_angle = STEERING_ANGLE_CENTER;
int old_steering_angle = STEERING_ANGLE_CENTER;
bool analog_throttle = false// false = use "One" button as throttle
int throttle_angle = THROTTLE_ANGLE_CENTER;
int gear = GEAR_1ST;
int pulse_steering;
int pulse_esc;
void myapp(void) {
#if SERIAL_DEBUG
  Serial.print("\r\n");
#endif
  /* Steering */
  steering_angle = getSteeringAngle();
  pulse_steering = map(steering_angle,
                       STEERING_ANGLE_MIN, STEERING_ANGLE_MAX,
                       SERVO_PULSE_MAX, SERVO_PULSE_MIN);
  SteeringServo.writeMicroseconds(pulse_steering);
  //delay(15);
  Serial.print("\tServo=");
  Serial.print(pulse_steering);
  /* Brake and Throttle */
  if (wiiremote.buttonPressed(WIIREMOTE_ONE)) {
    if (pulse_esc < ESC_PULSE_NEUTRAL) {
      // moving forward before press "One"
      brake();
      pulse_esc = ESC_PULSE_NEUTRAL;
    } else {
      // while stopping or moving backward, keep moving backward
      pulse_esc = ESC_PULSE_REV_FIX;
    }
  } else {
    if (analog_throttle) {
      throttle_angle = getThrottleAngle();
      pulse_esc = map(throttle_angle,
                      THROTTLE_ANGLE_MIN, THROTTLE_ANGLE_MAX,
                      ESC_PULSE_FWD_MIN, ESC_PULSE_FWD_MAX);
    } else if (wiiremote.buttonPressed(WIIREMOTE_TWO)) {
      switch (gear) {
       case GEAR_1ST:
        pulse_esc = ESC_PULSE_FWD_1ST;
        break;
       case GEAR_2ND:
        pulse_esc = ESC_PULSE_FWD_2ND;
        break;
       case GEAR_3RD:
        pulse_esc = ESC_PULSE_FWD_3RD;
        break;
       default:
        pulse_esc = ESC_PULSE_NEUTRAL;
        break;
      }
    } else {
      pulse_esc = ESC_PULSE_NEUTRAL;
    }
  }
  ESC.writeMicroseconds(pulse_esc);
  //delay(15);
  Serial.print("\tESC=");
  Serial.print(pulse_esc);
  /* Throttle mode */
  if (wiiremote.buttonClicked(WIIREMOTE_A)) {
    analog_throttle = !analog_throttle;
    if (analog_throttle) {
      wiiremote.setLED(WIIREMOTE_LED4); // analog mode
    } else {
      wiiremote.setLED(WIIREMOTE_LED1); // fixed mode, 1st gear
      gear = GEAR_1ST;
    }
  }
  /* Shift up or down */
  if (!analog_throttle) {
    if (wiiremote.buttonClicked(WIIREMOTE_RIGHT)) {
      shiftUp();
    } else if (wiiremote.buttonClicked(WIIREMOTE_LEFT)) {
      shiftDown();
    }
  }
} // myapp
int getSteeringAngle(void) {
  double rad;
  int deg;
  rad = acos((double) wiiremote.Report.Accel.Y);
  deg = (int) (rad * 180.0 / PI);
  /* clipping */
  if (deg > STEERING_ANGLE_MAX) { deg = STEERING_ANGLE_MAX; }
  if (deg < STEERING_ANGLE_MIN) { deg = STEERING_ANGLE_MIN; }
  return deg;
}
int getThrottleAngle(void) {
  double rad;
  double compensate_z;
  int deg;
    rad = asin((double) wiiremote.Report.Accel.Y);
    compensate_z = (double) wiiremote.Report.Accel.Z / cos(rad);
    rad = asin(compensate_z);
    deg = (int) (rad * 180.0 / PI);
  /* clipping */
  if (deg > THROTTLE_ANGLE_MAX) { deg = THROTTLE_ANGLE_MAX; }
  if (deg < THROTTLE_ANGLE_MIN) { deg = THROTTLE_ANGLE_MIN; }
  return deg;
}
inline void brake(void)
{
    ESC.writeMicroseconds(ESC_PULSE_BRAKE);
    delay(15);
    ESC.writeMicroseconds(ESC_PULSE_NEUTRAL);
    delay(15);
}
inline void shiftUp(void)
{
    if (gear < GEAR_3RD) {
        gear++;
        wiiremote.setLED( (WIIREMOTE_LED1 << (gear - GEAR_1ST)) );
    }
}
inline void shiftDown(void)
{
    if (gear > GEAR_1ST) {
        gear--;
        wiiremote.setLED( (WIIREMOTE_LED1 << (gear - GEAR_1ST)) );
    }
}

Leave a Reply