Friday, July 6, 2012

Connecting a Wii Nunchuck to a Raspberry Pi

Just to try something out with my Raspberry Pi I thought I'd check out its I2C capability and connect a Wii Nunchuck to it since they both run on 3V3 logic. Turned out it's quite a piece of cake once you have all the necessary parts.

Parts:

  • Raspberry Pi running Chris Boot's kernel to enable I2C.
  • Wii Nunchuck.
  • Some way to connect to the Nunchuck's pins. I use a WiiChuck adapter.
  • Some way to connect the Pi to the adapter. I use a breadboard, a ribbon cable, connectors and jumper wires.

The connection is standard I2C:

  • Pi pin 1 to adapter "+".
  • Pi pin 6 to adapter "-".
  • Pi pin 3 to adapter "d".
  • Pi pin 5 to adapter "c".

Steps:

  • Follow the instructions on Chris Boot's site to make sure that the i2c_dev kernel module is loaded.
  • Check if i2cdetect can see the Nunchuck:
    $ i2cdetect 0
    WARNING! This program can confuse your I2C bus, cause data loss and worse!
    I will probe file /dev/i2c-0.
    I will probe address range 0x03-0x77.
    Continue? [Y/n] 
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          03 04 05 06 07 -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- 52 -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- --                         
    
  • Here's a test code to read the sensor values from Nunchuck:
    #include <stdio.h>
    #include <stdlib.h>
    #include <linux/i2c-dev.h>
    #include <fcntl.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <signal.h>
    
    #define I2C_DEV "/dev/i2c-0"
    
    #define NUNCHUCK_ADDR 0x52
    
    #define MASK_BUTTON_Z 0x01
    #define MASK_BUTTON_C 0x02
    #define MASK_ACCEL_X 0x0C
    #define MASK_ACCEL_Y 0x40
    #define MASK_ACCEL_Z 0xC0
    
    #define BUTTON_Z(a) (a & MASK_BUTTON_Z)
    #define BUTTON_C(a) ((a & MASK_BUTTON_C) >> 1)
    
    #define ACCEL_X(a, b) ((a << 2) | ((b & MASK_ACCEL_X) >> 2))
    #define ACCEL_Y(a, b) ((a << 2) | ((b & MASK_ACCEL_X) >> 4))
    #define ACCEL_Z(a, b) ((a << 2) | ((b & MASK_ACCEL_X) >> 6))
    
    int fd = 0;
    
    void sigintHandler(int sigNum) {
            if (fd > 0) {
                    close(fd);
            }
    
            psignal(sigNum, "");
    
            exit(0);
    }
    
    int init() {
            char buf[2];
    
            if ((fd = open(I2C_DEV, O_RDWR)) < 0) {
                    return (-1);
            }
    
            if (ioctl(fd, I2C_SLAVE, NUNCHUCK_ADDR) < 0) {
                    close(fd);
                    return (-1);
            }
    
            buf[0] = 0xF0;
            buf[1] = 0x55;
    
            if (write(fd, buf, 2) < 0) {
                    close(fd);
                    return (-1);
            }
    
            buf[0] = 0xFB;
            buf[1] = 0x00;
    
            if (write(fd, buf, 2) < 0) {
                    close(fd);
                    return (-1);
            }
    
            return (0);
    }
    
    int readId(char *buf) {
            buf[0] = 0xFA;
    
            if (write(fd, buf, 1) < 0) {
                    perror("write");
                    close(fd);
                    exit(1);
            }
    
            if (read(fd, buf, 6) < 0) {
                    perror("read");
                    close(fd);
                    exit(1);
            }
    }
    
    int sendRequest() {
            char buf[1];
    
            buf[0] = 0x00;
    
            if (write(fd, buf, 1) < 0) {
                    close(fd);
                    return (-1);
            }
    }
    
    int readData(char *buf) {
            if (read(fd, buf, 6) < 0) {
                    return (-1);
            }
    
            sendRequest();
    
            return (0);
    }
    
    int main(int argc, char *argv[]) {
            char buf[6] = {0, 0, 0, 0, 0, 0};
            int accelX;
            int accelY;
            int accelZ;
            int buttonC;
            int buttonZ;
    
            signal(SIGINT, sigintHandler);
            signal(SIGTERM, sigintHandler);
    
            if (init() < 0) {
                    perror("init");
                    exit(1);
            }
    
            if (readId(buf) < 0) {
                    perror("readId");
                    close(fd);
                    exit(1);
            }
    
            printf("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
    
            while (1) {
                    if (readData(buf) < 0) {
                            perror("readData");
                            close(fd);
                            exit(1);
                    }
    
                    printf("%d/%d", buf[0], buf[1]);
    
                    accelX = ACCEL_X(buf[2], buf[5]);
                    accelY = ACCEL_X(buf[3], buf[5]);
                    accelZ = ACCEL_X(buf[4], buf[5]);
    
                    printf("; %d/%d/%d", accelX, accelY, accelZ);
    
                    buttonZ = BUTTON_Z(buf[5]);
                    buttonC = BUTTON_C(buf[5]);
    
                    printf ("; %s/%s", (buttonZ ? "z" : "Z"), (buttonC ? "c" : "C"));
    
                    printf("\n");
    
                    usleep(100 * 1000);
            }
    }
    
  • The output should look like this:
    0xff 0x00 0xa4 0x20 0x00 0x00 <<< The nunchuck's ID
    120/132; 333/521/589; z/c <<< Joystick X/Y; Accelerator X/Y/Z; Button Z/C
    ...
    
    If you move the joystick around, shake the nunchuck or press the buttons, you should see the output changes.

That's about it, really, quite simple. The kernel driver handles all the I2C details, so all that needs to be done is figure out the command sequence to initialise and read data from the nunchuck.

FWIW, HTH.

Thanks.

3 comments:

  1. Another Uninteresting and Unmotivating post about Connecting a Wii Classic Controller to a Raspberry Pi
    http://hdaniel67.blogspot.mx/2013/10/wii-classic-controller.html

    ReplyDelete
  2. I think there's a typo, you use ACCEL_X three times, where it should be ACCEL_X, ACCEL_Y and ACCEL_Z. Works fine on a olimex-A20 board with their uext-nunchuck adapter. Thank you for sharing.

    ReplyDelete
    Replies
    1. I suspect you're right. It's been a while. I'll retest it and update the post as necessary.

      Thanks.

      Delete