main index Wonderful: connecting a Nintendo Nunchuck to the BeagleBone and polling its values ("C" and "Z" keys, joystick x/y axis, and x/y/z accelerometer data) under Linux.

Note: my Arduino-related pages start here! (I already tried Arduino+Nunchuck a while ago), while my BeagleBoard-related pages start here.

HARDWARE

Before even considering to buy a Nintendo Wii, I bought an original Nintendo Nunchuck because I read about its easy-to-decode protocol on a 3.3V I²C port (also known as "TWI" - two wire interface).

And I happen to love BeagleBoards and BeagleBones.

BeagleBone has lots of 3.3V GPIOs and ports (while BeagleBoard has 1.8V ones); this means that I can connect the Nunchuck directly to the BeagleBone without needing "level converters".

The Nunchuck works at 3.3V and apparently it is "5V-tolerant" (I mean it! using it at 5 volts will at least shorten its lifespan).

Nunchuck connector:
| 1 2 3 |
|       |
| 6 5 4 |
|_-----_|
Connections from NunChuck to BeagleBone "P9" connector:


BEFORE RUNNING THE SOFTWARE

On my BeagleBone I am using the Arch Linux ARM distribution, at that time it was sporting a Linux 3.2.0 kernel (which names i2c-3 the I2C2 port of the BeagleBone). Everything below will also apply to some other Linux distributions, for example the Angstrom Linux.

First, to ease debugging, we need the i2c-tools package (it is not needed to run the C program; it is only needed for "i2cdetect" command to verify that the Nunchuck is connected). I was unable to install it, so I grabbed its sources from lm-sensors.org website, compiled them and installed on my BeagleBone.

Next, I made the Linux kernel aware of a new device called "nunchuck" ready on the port i2c-3 at address 0x52 (the I²C address on which every Nintendo Nunchuck works; some people shift it by one bit, having 0xa4, but the I²C addressable range is only 7-bit wide, from 0x03 to 0x77, so the correct vale is 0x52):
echo nunchuck 0x52 > /sys/class/i2c-adapter/i2c-3/new_device

Then with the i2cdetect command I read the port values before and after connecting it ("-y" means "don't bother asking yes/no"; "-r" means "use SMBus normal writes instead of SMBus Quick Writes"; "3" is the I²C port). Before:

i2cdetect -y -r 3
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

After:

i2cdetect -y -r 3
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- 52 -- UU UU UU UU -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

As you can see, in the location "52 (hex)", a "52 (hex)" appeared telling you that the Nunchuck is connected. Before reading data, it must be setup using the command 0x40/0x00.


MAKEFILE

Below, my generic makefile for a nunchuck program using a nunchuck device at a specified I²C port on a specified address. Beware of tab characters after the "target" lines:


PROG=nunchuck
PORT=3
ADDR=0x52

I2CP=i2c-$(PORT)
I2CD=/sys/class/i2c-adapter/$(I2CP)
I2CH=$(shell printf %04x $(ADDR))

all:: $(PROG) run

$(PROG): $(PROG).c
        gcc -s -O2 -DPORT=\"/dev/$(I2CP)\" -DADDR=$(ADDR) $(PROG).c -o $(PROG)

del:
        test -d $(I2CD)/$(PORT)-$(I2CH) && echo $(ADDR) > $(I2CD)/delete_device
        i2cdetect -y -r $(PORT)

i2c:
        test -d $(I2CD)/$(PORT)-$(I2CH) || echo $(PROG) $(ADDR) > $(I2CD)/new_device
        i2cdetect -y -r $(PORT)

clean:
        rm -f $(PROG)

run:
        ./$(PROG)

Asking for make i2c will create the device and show the detected peripherals on the PORT. Asking for make all will compile and run the PROGram, whose source is below.

After "creating" the device, you will see that under /sys/class/i2c-adapter/ in the corresponding i2c directory there is an 1-0052 directory with its system files (virtual files - they actually do not use diskspace, and "die" when you shutdown Linux).

Possible error codes while experimenting:

While running, accelerometer values (and possibly joystick values) could be somewhat jittering, due to natural "white noise". If you implement some Kalman-filtering on accelerometer data, I'll be glad to try it :-)

Note that the acceleration values are 10 bit wide (theoretically valued from 0 to 1023, with "quiet" position somewhat around 512). Also note that the one of the three axis, following the gravity acceleration, will never appear "centered" on the medium value 512, depending on the orientation of the Nunchuck.

Joystick values also are somewhat approximate - the absolute center may not be the "128" value, and the range may not be 0 to 255; for example, I see some 32 to 227 on vertical (y) axis, and some 21 to 230 on horizontal (x) axis.



SOURCE

The program below will try to initialize the Nunchuck on I2C2 port of the BeagleBone (on my current Arch Linux ARM distribution it is called "i2c-3", but on the BeagleBone SRM is called "I2C2").

Note that using an I²C port in Linux is easy as doing an open, an ioctl to set the peripheral address (beacuse on the same I²C bus you may connect more than one peripheral) and then normal read/write data. In this example I write one byte at a time.

The Nunchuck requires two bytes to start emitting data packets, and a single zero-valued byte to prepare next data packet. Packets are 6-bytes wide with a weak "encryption" (that "XOR 0x17 - 0x17") and a somewhat weird data packing:


//
//      nunchuck.c
//

#ifndef PORT
//#define PORT "/dev/i2c-1"    // reserved for capes and requiring pullup resistors
#define PORT "/dev/i2c-3"      // p9:pin19=SCL, p9:pin20:SDA
#endif

#ifndef ADDR
#define ADDR  0x52            // wii nunchuck address: 0x52
#endif

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>

#define error(x...) { fprintf(stderr, "\nE%d: ", __LINE__); fprintf(stderr, x); fprintf(stderr, "\n\n"); exit(1); }


int main()
{
  // initialization: open port, ioctl address, send 0x40/0x00 init to nunchuck:
  int fd = open(PORT, O_RDWR), i=0, n=0, readcnt=1;
  if(fd<0) error("cant open %s - %m", PORT);
  if(ioctl(fd, I2C_SLAVE, ADDR) < 0) error("cant ioctl %s:0x%02x - %m", PORT, ADDR);
  if(write(fd, "\x40", 2)<0) error("cant setup %s:0x%02x - %m", PORT, ADDR);

  // loop: read 6 bytes, parse, print
  unsigned char buf[6];
  for(;;)
  {
    n = read(fd, buf+i, 1); // read one byte (at index i)
    if(n<0) error("read error %s:0x%02x - %m", PORT, ADDR);
    if(!n) continue;
    buf[i] = (buf[i]^0x17)+0x17;  // decode incoming byte
    if(++i<6) continue;     // continue to read until a packet is complete
    i=0;                    // reset the index

    // 6-byte packet complete
    printf("packet %d: ", readcnt++);
    //for(n=0; n<6; n++) printf("%02x ", buf[n]);
    printf("   jx:%-3d  jy:%-3d  ", buf[0], buf[1]);  // joystick x/y values
    printf("%c %c  ", buf[5]&1 ? '.' : 'Z', buf[5]&2 ? '.' : 'C');  // keys Z/C
    for(n=1; n<=3; n++) printf("a%c:%-4d ", 'w'+n,
      (buf[n+1]<<2)+((buf[5]>>(n+n))&3));         // accelerometer x/y/z values

    write(fd, "", 1);       // send a zero to nunchuck to acknowledge the packet
    printf("\n");
  } 
}

Oh, yeah, in the photo below you see the Nunchuck Breakout by labs.thingm.com which I bought to not to cut the Nunchuck cable... :-) It seems that a smaller version is available on adafruit.com.


Beaglebone and Nunchuck

home page - send e-mail