Instructables

Arduino animatronics- make your awesome costumes more awesome!

FeaturedContest WinnerContest Winner

Step 10: Nunchuck control

Picture of Nunchuck control
Example6Arduino.jpg
Example 6- Using a Wii nunchuck as an input device

I wrote this bit of code back in 2007 to use a Wii nunchuck as an input device for an animatronic Predator cannon (see example 7.) The Wii nunchuck communicates to an Arduino over four wires (power, ground, data and clock) using an I²C interface (Inter-Integrated Circuit aka two-wire interface or TWI.)

The Wii nunchuck has a three axis accelerometer, joystick and two push buttons- for $20 it's an awesome input device for Arduino projects. The code presented here is a further modification of the code by Tod Kurt that was presented in his Bionic Arduino class- I simply extended it to control everything but the accelerometer Z axis, which I found I rarely used.

Using this code you can control four servos using the accelerometer and joystick functions and use the two push buttons to turn on LEDs (or transistors or even run a bit of code.)


/*
 * Example 6
 * Nunchuck control for four servos and two button inputs
 * Honus 2007
 * This allows the use of a Wii nunchuck as an input device and is modified/extended from the original code
 * by Tod E. Kurt and Windmeadow Labs
 *2007 Tod E. Kurt, http://todbot.com/blog/
 *The Wii Nunchuck reading code is taken from Windmeadow Labs, http://www.windmeadow.com/node/42
 */

#include "Wire.h" 

int ledPin1 = 13;       // Control pin for LED 1
int ledPin2 = 12;       // Control pin for LED 2
int servoPin1 = 9;      // Control pin for servo motor
int servoPin2 = 8;      // Control pin for servo motor
int servoPin3 = 7;      // Control pin for servo motor
int servoPin4 = 6;      // Control pin for servo motor

int pulseWidth1 = 0;    // Amount to pulse the servo 1
int pulseWidth2 = 0;    // Amount to pulse the servo 2
int pulseWidth3 = 0;    // Amount to pulse the servo 3
int pulseWidth4 = 0;    // Amount to pulse the servo 4

int refreshTime = 20;  // the time in millisecs needed in between pulses

long lastPulse1;
long lastPulse2;
long lastPulse3;
long lastPulse4;

int minPulse = 700;   // minimum pulse width
int loop_cnt=0;

void setup()
{
  Serial.begin(19200);
  pinMode(servoPin1, OUTPUT);  // Set servo pin as an output pin
  pinMode(servoPin2, OUTPUT);  // Set servo pin as an output pin
  pinMode(servoPin3, OUTPUT);  // Set servo pin as an output pin
  pinMode(servoPin4, OUTPUT);  // Set servo pin as an output pin
 
  pulseWidth1 = minPulse;      // Set the motor position to the minimum
  pulseWidth2 = minPulse;      // Set the motor position to the minimum
  pulseWidth3 = minPulse;      // Set the motor position to the minimum
  pulseWidth4 = minPulse;      // Set the motor position to the minimum

  nunchuck_init(); // send the initilization handshake
  Serial.print("NunchuckServo ready\n");
}

void loop()
{
  checkNunchuck1();
  updateServo1();   // update servo 1 position
  checkNunchuck2();
  updateServo2();   // update servo 2 position
  checkNunchuck3();
  updateServo3();   // update servo 3 position
  checkNunchuck4();
  updateServo4();   // update servo 4 position

     if( nunchuck_zbutton() )      // light the LED if z button is pressed
    digitalWrite(ledPin1, HIGH);
  else
    digitalWrite(ledPin1,LOW);
   
     if( nunchuck_cbutton() )      // light the LED if c button is pressed
    digitalWrite(ledPin2, HIGH);
  else
    digitalWrite(ledPin2,LOW);

  delay(1);        // this is here to give a known time per loop
}


void checkNunchuck1()
{
  if( loop_cnt > 100 ) {  // loop()s is every 1msec, this is every 100msec
   
    nunchuck_get_data();
    nunchuck_print_data();

    float tilt = nunchuck_accelx();    // x-axis, in this case ranges from ~70 - ~185
    tilt = (tilt - 70) * 1.5;        // convert to angle in degrees, roughly 
    pulseWidth1 = (tilt * 9) + minPulse; // convert angle to microseconds
   
    loop_cnt = 0;  // reset for
  }
  loop_cnt++;
 
}

// called every loop().
// uses global variables servoPin, pulsewidth, lastPulse, & refreshTime
void updateServo1()
{
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPulse1 >= refreshTime) {
    digitalWrite(servoPin1, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth1);   // Length of the pulse sets the motor position
    digitalWrite(servoPin1, LOW);    // Turn the motor off
    lastPulse1 = millis();            // save the time of the last pulse
  }
}

void checkNunchuck2()
{
  if( loop_cnt > 100 ) {  // loop()s is every 1msec, this is every 100msec
   
    nunchuck_get_data();
    nunchuck_print_data();

    float tilt = nunchuck_accely();    // y-axis, in this case ranges from ~70 - ~185
    tilt = (tilt - 70) * 1.5;        // convert to angle in degrees, roughly 
    pulseWidth2 = (tilt * 9) + minPulse; // convert angle to microseconds
   
    loop_cnt = 0;  // reset for
  }
  loop_cnt++;
 
}

// called every loop().
// uses global variables servoPin, pulsewidth, lastPulse, & refreshTime
void updateServo2()
{
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPulse2 >= refreshTime) {
    digitalWrite(servoPin2, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth2);   // Length of the pulse sets the motor position
    digitalWrite(servoPin2, LOW);    // Turn the motor off
    lastPulse2 = millis();            // save the time of the last pulse
  }
}

void checkNunchuck3()
{
  if( loop_cnt > 100 ) {  // loop()s is every 1msec, this is every 100msec
   
    nunchuck_get_data();
    nunchuck_print_data();

    float tilt = nunchuck_joyx();    // x-axis, in this case ranges from ~70 - ~185
    tilt = (tilt - 70) * 1.5;        // convert to angle in degrees, roughly 
    pulseWidth3 = (tilt * 9) + minPulse; // convert angle to microseconds
   
    loop_cnt = 0;  // reset for
  }
  loop_cnt++;
 
}

// called every loop().
// uses global variables servoPin, pulsewidth, lastPulse, & refreshTime
void updateServo3()
{
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPulse3 >= refreshTime) {
    digitalWrite(servoPin3, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth3);   // Length of the pulse sets the motor position
    digitalWrite(servoPin3, LOW);    // Turn the motor off
    lastPulse3 = millis();            // save the time of the last pulse
  }
}

void checkNunchuck4()
{
  if( loop_cnt > 100 ) {  // loop()s is every 1msec, this is every 100msec
   
    nunchuck_get_data();
    nunchuck_print_data();

    float tilt = nunchuck_joyy();    // y-axis, in this case ranges from ~70 - ~185
    tilt = (tilt - 70) * 1.5;        // convert to angle in degrees, roughly 
    pulseWidth4 = (tilt * 9) + minPulse; // convert angle to microseconds
   
    loop_cnt = 0;  // reset for
  }
  loop_cnt++;
 
}

// called every loop().
// uses global variables servoPin, pulsewidth, lastPulse, & refreshTime
void updateServo4()
{
  // pulse the servo again if rhe refresh time (20 ms) have passed:
  if (millis() - lastPulse4 >= refreshTime) {
    digitalWrite(servoPin4, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth4);   // Length of the pulse sets the motor position
    digitalWrite(servoPin4, LOW);    // Turn the motor off
    lastPulse4 = millis();            // save the time of the last pulse
  }
}


//
// Nunchuck functions
//

static uint8_t nunchuck_buf[6];   // array to store nunchuck data,


// initialize the I2C system, join the I2C bus,
// and tell the nunchuck we're talking to it
void nunchuck_init()
{
  Wire.begin();                    // join i2c bus as master
  Wire.beginTransmission(0x52);    // transmit to device 0x52
  Wire.send(0x40);        // sends memory address
  Wire.send(0x00);        // sends sent a zero. 
  Wire.endTransmission();    // stop transmitting
}

// Send a request for data to the nunchuck
// was "send_zero()"
void nunchuck_send_request()
{
  Wire.beginTransmission(0x52);    // transmit to device 0x52
  Wire.send(0x00);        // sends one byte
  Wire.endTransmission();    // stop transmitting
}

// Receive data back from the nunchuck,
// returns 1 on successful read. returns 0 on failure
int nunchuck_get_data()
{
  int cnt=0;
  Wire.requestFrom (0x52, 6);    // request data from nunchuck
  while (Wire.available ()) {
    // receive byte as an integer
    nunchuck_buf[cnt] = nunchuk_decode_byte(Wire.receive());
    cnt++;
  }
  nunchuck_send_request();  // send request for next data payload
  // If we recieved the 6 bytes, then go print them
  if (cnt >= 5) {
    return 1;   // success
  }
  return 0; //failure
}

// Print the input data we have recieved
// accel data is 10 bits long
// so we read 8 bits, then we have to add
// on the last 2 bits.  That is why I
// multiply them by 2 * 2
void nunchuck_print_data()
{
  static int i=0;
  int joy_x_axis = nunchuck_buf[0];
  int joy_y_axis = nunchuck_buf[1];
  int accel_x_axis = nunchuck_buf[2]; // * 2 * 2;
  int accel_y_axis = nunchuck_buf[3]; // * 2 * 2;
  int accel_z_axis = nunchuck_buf[4]; // * 2 * 2;

  int z_button = 0;
  int c_button = 0;

  // byte nunchuck_buf[5] contains bits for z and c buttons
  // it also contains the least significant bits for the accelerometer data
  // so we have to check each bit of byte outbuf[5]
  if ((nunchuck_buf[5] >> 0) & 1)
    z_button = 1;
  if ((nunchuck_buf[5] >> 1) & 1)
    c_button = 1;

  if ((nunchuck_buf[5] >> 2) & 1)
    accel_x_axis += 2;
  if ((nunchuck_buf[5] >> 3) & 1)
    accel_x_axis += 1;

  if ((nunchuck_buf[5] >> 4) & 1)
    accel_y_axis += 2;
  if ((nunchuck_buf[5] >> 5) & 1)
    accel_y_axis += 1;

  if ((nunchuck_buf[5] >> 6) & 1)
    accel_z_axis += 2;
  if ((nunchuck_buf[5] >> 7) & 1)
    accel_z_axis += 1;

  Serial.print(i,DEC);
  Serial.print("\t");

  Serial.print("joy:");
  Serial.print(joy_x_axis,DEC);
  Serial.print(",");
  Serial.print(joy_y_axis, DEC);
  Serial.print("  \t");

  Serial.print("acc:");
  Serial.print(accel_x_axis, DEC);
  Serial.print(",");
  Serial.print(accel_y_axis, DEC);
  Serial.print(",");
  Serial.print(accel_z_axis, DEC);
  Serial.print("\t");

  Serial.print("but:");
  Serial.print(z_button, DEC);
  Serial.print(",");
  Serial.print(c_button, DEC);

  Serial.print("\r\n");  // newline
  i++;
}

// Encode data to format that most wiimote drivers except
// only needed if you use one of the regular wiimote drivers
char nunchuk_decode_byte (char x)
{
  x = (x ^ 0x17) + 0x17;
  return x;
}

// returns zbutton state: 1=pressed, 0=notpressed
int nunchuck_zbutton()
{
   return ((nunchuck_buf[5] >> 0) & 1) ? 0 : 1;  // voodoo
}

// returns zbutton state: 1=pressed, 0=notpressed
int nunchuck_cbutton()
{
   return ((nunchuck_buf[5] >> 1) & 1) ? 0 : 1;  // voodoo
}

// returns value of x-axis joystick
int nunchuck_joyx()
{
  return nunchuck_buf[0];
}

// returns value of y-axis joystick
int nunchuck_joyy()
{
  return nunchuck_buf[1];
}

// returns value of x-axis accelerometer
int nunchuck_accelx()
{
  return nunchuck_buf[2];   // FIXME: this leaves out 2-bits of the data
}

// returns value of y-axis accelerometer
int nunchuck_accely()
{
  return nunchuck_buf[3];   // FIXME: this leaves out 2-bits of the data
}

// returns value of z-axis accelerometer
int nunchuck_accelz()
{
  return nunchuck_buf[4];   // FIXME: this leaves out 2-bits of the data
}