이젠 벽돌이 되어버린 니텐도 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,
};
Wii
Remote 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)) );
}
}