Android IOIO Wii Nunchuck
July 24, 2011 12 Comments
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 :
And here is the whole project:
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:
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>
Pingback: RC Car Electronics | Robotics / Electronics / Physical Computing
Would there be a way to have two of them ?
And a way to communicate over bluetooth or wifi ?
this would be a great setup with gearVR …
The nunchucks all have the same I2C address so can’t connect them on the same bus, BUT I’m pretty sure the IOIO has more than 1 I2C bus, so it should be doable.
Newer IOIOs I think connect to your phone through Bluetooth, so yes..
Could you Play games with this, or can you just move the red Ball?
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
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)
Which inputs of the IOIO did you use for Data and Clock?
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
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
Do let me know how it goes !
Dan
Pingback: Android IOIO Wii Motion Plus – Gyroscopes | PDA8
Pingback: Android IOIO Wii Motion Plus – Gyroscopes « Robotics / Electronics / Physical Computing