Android IOIO Wii Nunchuck


I’ve already written a post about how to connect the Wii Nunchuck to the Lego NXT using LeJOS, and there is also information on my blog about how to do the same thing with an Arduino (though I can’t find it right now, so no easy link…).

It’s only fair that I explore the same thing with an Android phone and the IOIO board.

The IOIO board is great !

I’ve already done a quick Hello World project, and a slightly more advanced one, controlling a RC Servo.

I’m keen on testing the I2C features… and what better way than trying to connect a Wii peripheral to the board !?

The Wii Nunchuck I’m using has had it’s PULL-UP resistors removed, because they were of quite low value and the Lego NXT couldn’t cope with it.

So now I’m using two2 2.2kOhms ones, connected directly from the Data and Clock lines to 3.3V.

The IOIO board works at 3.3V so this is perfect for the nunchuck.

Here’s an image of the wiring, using the Nunchucky 1.0 adapter from Solarbotics :

Wii Nunchuck connected to the IOIO board - Nunchucky 1.0 adapter from Solarbotics

And here is the whole project:

Android phone + IOIO board + Wii Nunchuck + Battery

Once you the data from the nunchuck is retrieved on the Android device, one can obviously do plently of useful things with it, but in my case I’ve just quickly put together a very basic 2D animation, that simply moved a red ball according to the nunchuck’s joystick:

Android IOIO - Wii Nunchuck - 2D animation

And here is the Android Code

First, the IOIONunchack class, that connects through I2C and does the data decoding:


package com.trandi.wiinunchuck;

import ioio.lib.api.IOIO;
import ioio.lib.api.TwiMaster;
import ioio.lib.api.exception.ConnectionLostException;

public class IOIONunchuck {
private int joy_x_axis;
private int joy_y_axis;
private int accel_x_axis;
private int accel_y_axis;
private int accel_z_axis;
private boolean z_button = false;
private boolean c_button = false;

private TwiMaster _twi;
private static final int WII_NUN_ADDR = 0x52;  // 7bit addressing.  It would be 0xA4 in 8bit
private static final int WII_NUN_MEM_ADDR = 0x40;
private final byte[] _dataBuff = new byte[6];// array to store nunchuck data

public boolean init(IOIO ioio) throws ConnectionLostException, InterruptedException {
// initialize the I2C system, join the I2C bus,
_twi = ioio.openTwiMaster(0, TwiMaster.Rate.RATE_100KHz, false);

// tell the nunchuck we're talking to it
return _twi.writeRead(WII_NUN_ADDR, false, new byte[]{WII_NUN_MEM_ADDR, 0x00}, 2, new byte[]{}, 0);
}

public boolean readData() throws ConnectionLostException, InterruptedException {
if(readRawData()){
for(int i=0; i<_dataBuff.length; i++) _dataBuff[i] = nunchuckDecodeByte(_dataBuff[i]);

// transform into something meaningful
joy_x_axis = _dataBuff[0];
joy_y_axis = _dataBuff[1];
accel_x_axis = _dataBuff[2];
accel_y_axis = _dataBuff[3];
accel_z_axis = _dataBuff[4];

// byte nunchuck_buf[5] contains bits for z and c buttons
z_button = (_dataBuff[5] & 0x01) == 0;
c_button = (_dataBuff[5] & 0x02) == 0;

// it also contains the least significant bits for the accelerometer data so we have to check each bit of byte outbuf[5]
if ((_dataBuff[5] & 0x03) > 0) accel_x_axis += 2;
if ((_dataBuff[5] & 0x04) > 0) accel_x_axis += 1;

if ((_dataBuff[5] & 0x05) > 0) accel_y_axis += 2;
if ((_dataBuff[5] & 0x06) > 0) accel_y_axis += 1;

if ((_dataBuff[5] & 0x07) > 0) accel_z_axis += 2;
if ((_dataBuff[5] & 0x08) > 0) accel_z_axis += 1;

joy_x_axis = updateRange(joy_x_axis);
joy_y_axis = updateRange(joy_y_axis);
accel_x_axis = updateRange(accel_x_axis);
accel_y_axis = updateRange(accel_y_axis);
accel_z_axis = updateRange(accel_z_axis);

return true;
}

return false;
}

private boolean readRawData() throws ConnectionLostException, InterruptedException {
// 1. Send a request for data to the nunchuck
if(_twi.writeRead(WII_NUN_ADDR, false, new byte[]{0x00}, 1, _dataBuff, 0)) {

Thread.sleep(10);

// 2. read data
return _twi.writeRead(WII_NUN_ADDR, false, new byte[]{}, 0, _dataBuff, _dataBuff.length);
}

return false;
}

/**
* Transforms a 0..127 -128 .. 0 range into a 128..0..-128 one
*/
private static int updateRange(int init){
int result = init;
boolean negative = result < 0;
if(negative) result = -result;
result = - result + 128;
if(negative) result = -result;
return result;
}

private byte nunchuckDecodeByte (byte x){
return (byte)((x ^ 0x17) + 0x17);
}

public int getJoy_x_axis() {
return joy_x_axis;
}

public int getJoy_y_axis() {
return joy_y_axis;
}

public int getAccel_x_axis() {
return accel_x_axis;
}

public int getAccel_y_axis() {
return accel_y_axis;
}

public int getAccel_z_axis() {
return accel_z_axis;
}

public boolean isZ_button() {
return z_button;
}

public boolean isC_button() {
return c_button;
}
}

Then, the MainActivity and the Ball classes, that do all the Android plubing :


______________ MainActivity.java

package com.trandi.wiinunchuck;

import ioio.lib.api.PwmOutput;
import ioio.lib.api.exception.ConnectionLostException;
import ioio.lib.util.AbstractIOIOActivity;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.TextView;

public class MainActivity extends AbstractIOIOActivity {
private TextView _varField;
private TextView _msg;
private FrameLayout _drawingFrame;
private Ball _ball;

/**
* Called when the activity is first created. Here we normally initialize our GUI.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
//            @Override
//            public void uncaughtException(Thread thread, Throwable ex) {
//                Log.e("", "", ex);
//            }
//        });

setContentView(R.layout.main);
_varField = (TextView) findViewById(R.id.textViewVarVal);
_msg = (TextView) findViewById(R.id.textViewMsg);

_drawingFrame = (FrameLayout) findViewById(R.id.drawingFrame);
_ball = new Ball(_drawingFrame.getContext(), 15);
_drawingFrame.addView(_ball);
}

/**
* This is the thread on which all the IOIO activity happens. It will be run
* every time the application is resumed and aborted when it is paused. The
* method setup() will be called right after a connection with the IOIO has
* been established (which might happen several times!). Then, loop() will
* be called repetitively until the IOIO gets disconnected.
*/
class IOIOThread extends AbstractIOIOActivity.IOIOThread {
private final IOIONunchuck _nunchuck = new IOIONunchuck();
private PwmOutput _led; // The on-board LED
private boolean _ledOn = false;

/**
* Called every time a connection with IOIO has been established.
* Typically used to open pins.
*
* @throws ConnectionLostException When IOIO connection is lost.
*/
@Override
protected void setup() throws ConnectionLostException {
try {
_led = ioio_.openPwmOutput(0, 300);
_led.setDutyCycle(0);

_nunchuck.init(ioio_);
} catch (Exception e) {
msg(e);
}
}

/**
* Called repetitively while the IOIO is connected.
*
* @throws ConnectionLostException When IOIO connection is lost.
*/
@Override
protected void loop() throws ConnectionLostException {
try {
if(_nunchuck.readData()) {
_ball.setPos(mapPos(_nunchuck.getJoy_x_axis()), mapPos(_nunchuck.getJoy_y_axis()));

runOnUiThread(new Runnable() {
@Override
public void run() {
_varField.setText(String.valueOf(_nunchuck.getJoy_x_axis()));
refreshBall();
}
});

_led.setDutyCycle(_ledOn ? 1 : 0);
_ledOn = ! _ledOn;

Thread.sleep(100);
}
} catch (Exception e) {
msg(e);
}
}
}

private static int mapPos(int value) {
return 128 + value;
}

private void msg(Throwable e){
msg("ERR: " + e.getMessage());
}

private void refreshBall() {
runOnUiThread(new Runnable() {
@Override
public void run() {
_drawingFrame.removeView(_ball);
_drawingFrame.addView(_ball);
}
});
}

private void msg(final String msg){
runOnUiThread(new Runnable() {
@Override
public void run() {
_msg.setText("MSG:  " + msg);
}
});
}

/**
* A method to create our IOIO thread.
*
* @see ioio.lib.util.AbstractIOIOActivity#createIOIOThread()
*/
@Override
protected AbstractIOIOActivity.IOIOThread createIOIOThread() {
IOIOThread result = new IOIOThread();
return result;
}
}



_________ Ball.java

package com.trandi.wiinunchuck;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

public class Ball extends View {
private float x = 0;
private float y = 0;
private final int r;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

public Ball(Context context, int r) {
super(context);
mPaint.setColor(0xFFFF0000);
this.r = r;
}

public void setPos(float x, float y) {
this.x = x;
this.y = y;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, r, mPaint);
}
}

And finally, the layout XML, for completeness, even though I doubt is very useful :


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/AbsoluteLayout1" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ToggleButton android:text="ToggleButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button"></ToggleButton>
<TextView android:text="Var" android:id="@+id/textViewVar" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Var value" android:id="@+id/textViewVarVal" android:textAppearance="?android:attr/textAppearanceLarge" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView  android:text="Message" android:id="@+id/textViewMsg"  android:layout_height="wrap_content" android:layout_width="wrap_content"></TextView>
<FrameLayout android:id="@+id/drawingFrame" android:layout_height="match_parent" android:layout_width="match_parent"/>
</LinearLayout>

About these ads

9 Responses to Android IOIO Wii Nunchuck

  1. MrWater says:

    Could you Play games with this, or can you just move the red Ball?

    • trandi says:

      I guess you could play games, but you’ll need more programming on the Android side to simulate screen touches or phone movements with the data from the nunchuck.

      Dan

  2. Ad says:

    I burnt my IOIO (soldering error recently), now waiting for a new on. In the mean time I remembered I had this wireless nunchuck to test. I’m trying your code with last version of IOIOlib. AbstractIOIOActivity is reportedly deprecated (warning, not an error). Could it explain that “R” is not resolved at “setContentView(R.layout.main);”?
    Btw R.java is generated.
    (I’ve limited coding skills)

  3. Ad says:

    Which inputs of the IOIO did you use for Data and Clock?

    • trandi says:

      4 & 5 as you can seen on the pictures.
      They correspond to the 1st I2C bus (there are 3 of them I think ?) as you can see in this code snippet:

      _twi = ioio.openTwiMaster(0, TwiMaster.Rate.RATE_100KHz, false);

      Dan

      • Ad says:

        Thanks,
        3 I2C indeed, others are 25/26 (#2) and 47/48 (#1)
        I’ll ty a similar setup with a wireless nunchuk (unfortunatly not bluetooth… apparently), by connecting the teardowned plug to the IOIO.
        Adrien

      • trandi says:

        Do let me know how it goes !

        Dan

  4. Pingback: Android IOIO Wii Motion Plus – Gyroscopes | PDA8

  5. Pingback: Android IOIO Wii Motion Plus – Gyroscopes « Robotics / Electronics / Physical Computing

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 148 other followers

%d bloggers like this: