Skip to content


Wii Nunchuck Mouse Code Breakdown

In this article Frank explains the code used in the Wii Nunchuck Mouse article. Each piece of the code is explained clearly. If you want to understand how the Wii Nunchuck Mouse works or learn about programming then this is a good article to read.

Coverage includes the Arduino microcontroller code and the Autoit scripts used to provide mouse input to Windows.

Arduino Code Break Down

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // clear bit

#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // set bit

#include "C:\Documents and Settings\Administrator\My Documents\Arduino\nunchuckmousearduino\i2c.h" // change file path #include <math.h>

The cbi and sbi are macros, they are commonly used but I don’t remember where they are included so I just defined them myself.

There’s a I2C.h file I found, it’s actually the file used by the Wire.h library. You’ll need a copy somewhere on your computer and the file path must point to it.

Math.h is there, but it’s not used, I forgot to delete it.

// variables
int joyX;
int joyY;
int accelX;
int accelY;
int accelZ;
int xAngle;
int yAngle; byte _joyX;
byte _joyY; byte _accelX;
byte _accelY; byte _accelZ;
byte butC; byte butZ;
byte buttons;

Variable declarations, the ones with a underscore before them are raw data after encryption while the others are processed so that they are normalized between -127 and 127. ButC and ButZ are equal to 1 when they are pressed. Accel and angle are different, angle has gone through a sine function so they are true angles between -90 and 90.

  

void nunchuckInit() {

twi_init(); // initialize i2c

delay(10); // wait a bit

// send init to nunchuck

byte data[2];

data[0] = 0x40;

data[1] = 0x00;

twi_writeTo(0x52, data, 2, 1);

}

twi_init initializes the registers inside the AVR to start the I2C bus

The delay waits for everything on the nunchuck to be ready, it’s 10 ms so it’s not noticeable.

Data is an array containing 2 bytes, the first is 0x40 and the second is 0x00, these two will be sent to the nunchuck in order when twi_writeTo is called.

When calling twi_writeTo 0x52 is the address of the nunchuck, the two bytes from the data array is sent, and 1 at the end tells twi_writeTo to wait until it is finished until returning to other processes.

  

int chuckProc(byte _data, int min, int max) {

long data = _data;

// make sure within range

if(data < min) {

data = min;

}

else if(data > max) {

data = max;

}

// normalize values

data -= min;

data = (255 * ((data * 100) / (max - min))) / 100;

data -= 127;

// make sure within range again

if(data < -127) {

data = -127;

}

else if(data > 127) {

data = 127;

}

if(data < 15 && data > -15) {

data = 0;

}

int data_ = data;

return data_;

}

There’s nothing special about this function, what it does is normalize joystick and acceleration values between two numbers while making sure nothing overflows. It will output a number between -127 and 127, making it easy to use later. If a value is within 15 units from 0, it will be set to 0, this makes sure the joystick and acceleration doesn’t change due to vibrations and such. It does not handle decryption. This function is called later, where I will explain what min and max is and how to use them. 

void nunchuckRead() {

// request data

byte data[6];

twi_readFrom(0x52, data, 6);

// read data from buffer

byte i;

for(i = 0; i < 6; i++) {

data[i] = twi_masterBuffer[i];

}

This is the nunchuckRead function, which will read and process all data from the nunchuck, then placing them into global variables.

Data array length of 6 is created, twi_readFrom will send a read command to device 0x52 to read 6 bytes. Then the for loop places the data from the buffer into the data array. 

// decipher data

_joyX = (data[0] ^ 0x17) + 0x17;

_joyY = (data[1] ^ 0x17) + 0x17;

_accelX = (data[2] ^ 0x17) + 0x17;

_accelY = (data[3] ^ 0x17) + 0x17;

_accelZ = (data[4] ^ 0x17) + 0x17;

buttons = (data[5] ^ 0x17) + 0x17;

Raw data being decrypted, simple operation, first XOR byte by 0x17, then add 0x17 to it, since these are all 8 bit unsigned byte integers, adding the 0x17 will not cause problems if it goes over 255. 

// get ready for next request

data[0] = 0x00;

twi_writeTo(0x52, data, 1, 1);

Send one byte to the nunchuck so it's ready for the next read.

// process raw data into good usuable ranges and angles

joyX = chuckProc(_joyX, 33, 223);

joyY = chuckProc(_joyY, 34, 223);

accelX = chuckProc(_accelX, 0x48, 0xB0);

xAngle = accelToAngle(accelX);

accelY = chuckProc(_accelY, 0x46, 0xAF);

yAngle = accelToAngle(accelY);

accelZ = chuckProc(_accelZ, 0x4A, 0xB1);

buttons &= B00000011;

ChuckProc is called, as I said before, normalizing the raw data to a number between -127 and 127. Min and max are used for calibration purposes. Min and max are the raw values of the joystick and accelerometers when the joystick is pushed to it’s physical limit and when the accelerometers are at their maximum/minimum possible value. You should be setting these yourself, but have a look at http://www.windmeadow.com/node/42 which is where I got the original values from.

AccelToAngle is a function that’s a large trig lookup table (located in the file named accelToAngle, which contains a single function that returns an angle using a large switch-case lookup table), it returns the angle the controller is tilted at based on static acceleration. Only works on Earth gravity.

Note that “buttons &= B00000011;” is useless, and should be deleted if you plan on modifying my code.

// find buttons

if(bit_is_set(buttons, 1)) {

butC = 0;

}

else {

butC = 1;

}

if(bit_is_set(buttons, 0)) {

butZ = 0;

}

else {

butZ = 1;

}

}

Obvious enough, the bits in “buttons” are active low, but the butC and butZ are active high, meaning when the buttons are pressed, they are equal to 1, when they are released, they are equal to 0. 

void setup() {

Serial.begin(115200); // initialize serial port

nunchuckInit(); // initialize nunchuck

}

Setup will initialize the serial port at 115200 baud, then initialize the I2C bus and nunchuck by calling the nunchuckInit function which was discussed before.

void loop() {

nunchuckRead(); // read data from nunchuck

Inside loop, the first thing done is to read the data from the nunchuck by calling nunchuckRead.

  Serial.print(255, BYTE); // sync byte to computer

This is the synchronization byte sent to the computer, if this wasn’t here and the Arduino is reset or something malfunctions, then the computer simply waits for this byte until reading data again. This will make sure the computer software never gets stuck in a loop, or make your mouse go crazy because it’s reading the joyX as the mouse Y movement variable. This makes sure that will never happen.

  

if(butZ == 1) {

Serial.print("l"); // left click

}

else {

Serial.print(" ");

}

if(butC == 1) {

Serial.print("r"); // right click

}

else {

Serial.print(" ");

}

Z button for left click, C button for right click. If you are wondering why I didn’t just transfer both in 1 byte, my reason is that it’s easier and not too slow to do it this way instead of converting a decimal to binary on the computer side.

  

if(yAngle > 45 || yAngle < 0 - 45) {

// if tilted

if(joyY < 0 - 100) {

Serial.print("d"); // scroll down

}

else if(joyY > 100) {

Serial.print("u"); // scroll up

}

else {

if(joyX > 100) {

Serial.print("m"); // middle click

}

else if(joyX < 0 - 100) {

Serial.print("b"); // back button

}

else {

Serial.print(" ");

}

}

joyX = 0; // if tilted, don't move the cursor

joyY = 0;

}

else {

Serial.print(" ");

}

If the controller is tilted beyond 45 degrees, then override the joystick functions, so that up and down will emulate the scroll wheel, pushing the joystick right will cause a right click, and pushing the joystick left will cause a backspace operation (back button in Internet browsers).

Serial.print((joyX / 2) + (127 / 2), BYTE); // send joystick data

Serial.print((joyY / 2) + (127 / 2), BYTE);

Serial.println();

delay(50); // delay so no overflow on computer

Send the joystick data to the computer for mouse movement, but make it an integer between 0 and 128 so it never reaches 255. If it does reach 255, the computer might confuse it with a synchronization byte.

Serial.println is there to make debugging easier by making the data appear on new lines instead of back-to-back, doesn’t really matter if it’s there or not during normal usage.

The delay is pointless, but if your computer is too busy it might not be wise to keep sending data, adjust this if you think the mouse movement is too slow (I mean wayyy too slow since adjustments can also be made on the computer).

Autoit Code Break Down

#include "CommMG.au3"

$error = ""

If _CommListPorts(1) <> 1 and _CommListPorts(1) <> 2 Then

$port = StringReplace(_CommListPorts(1), "COM", "")

$port = StringSplit($port, '|', 1)

_CommSetPort($port[1],$error,115200,8,0,1,0) ; change 6 to whatever com port you need to use

HotKeySet("{ESC}", "quit")

Else

quit()

EndIf

Include the file needed to use the serial port.

Error is a variable, it’s useless but it’s needed as an argument to _CommSetPort

If there is a port avaliable, open it at 115200 baud, 8 bits, 1 stop bit, no parity.

Ignore the comment that says “change 6 to whatever com port you need to use”, I left it there by mistake when I was hard coding the port name in, you can change the port by changing the 1 inside $port[1].

HotKeySet is used to set ESC as the emergency quit hotkey, so if anything malfunctions, press ESC.

If no port is found, the program quits. 

Dim $temp

$temp = 0

; these are flags to avoid repeated actions

Dim $mruf = True

Dim $mrdf = False

Dim $mluf = True

Dim $mldf = False

Dim $mmuf = True

Dim $mmdf = False

Dim $mbuf = True

Dim $mbdf = False

Variable declarations, these are all Boolean, mruf means mouse right button up flag, these are used so that the program holds down or lifts up the mouse button instead of clicking it a thousand times per second. Temp is used to store the last byte received by the serial port.

  

While 1

While $temp <> 255 ; wait for sync byte

$temp = _CommReadByte(1)

WEnd

While 1 is the main loop of this program. Wend is found at the end of the program.

The program waits for the 255 synchronization byte, if the byte received isn’t 255, it waits until the next one is.

$temp = _CommReadChar(1)

If $temp == "l" Then

$mluf = False

If Not $mldf Then

MouseDown("left")

$mldf = True

EndIf

Else

$mldf = False

If Not $mluf Then

MouseUp("left")

$mluf = True

EndIf

Endif

$temp = _CommReadChar(1)

If $temp == "r" Then

$mruf = False

If Not $mrdf Then

MouseDown("right")

$mrdf = True

EndIf

Else

$mrdf = False

If Not $mruf Then

MouseUp("right")

$mruf = True

EndIf

Endif

$temp = _CommReadChar(1)

If $temp == "u" Then

MouseWheel("up", 1)

$mmdf = False

If Not $mmuf Then

MouseUp("middle")

$mmuf = True

EndIf

$mbdf = False

ElseIf $temp == "d" Then

MouseWheel("down", 1)

$mmdf = False

If Not $mmuf Then

MouseUp("middle")

$mmuf = True

EndIf

$mbdf = False

ElseIf $temp == "m" Then

$mmuf = False

If Not $mmdf Then

MouseDown("middle")

$mmdf = True

EndIf

$mbdf = False

ElseIf $temp == "b" Then

If Not $mbdf Then

Send("{BACKSPACE}")

$mbdf = True

EndIf

$mmdf = False

If Not $mmuf Then

MouseUp("middle")

$mmuf = True

EndIf

Else

$mmdf = False

If Not $mmuf Then

MouseUp("middle")

$mmuf = True

EndIf

$mbdf = False

Endif

Handles mouse buttons by reading whether or not the buttons on the nunchuck are pressed, if MouseDown or MouseUp is called twice in a row, it will cause a click operation, this is undesirable because it will happen hundreds of times per second, so the flags are used and these two functions are only called when the flags are properly set.

$temp = _CommReadByte(1)

$moveX = ($temp - 63) / 2

$temp = _CommReadByte(1)

$moveY = ($temp - 63) / 2

$mouse = MouseGetPos()

MouseMove($mouse[0] + ($moveX), $mouse[1] - ($moveY), 0)

Read joystick data, calculate, then read current mouse position, then calculate new mouse position, simple. Here you can make your adjustments to mouse speed.

$temp = _CommReadByte(1)

$temp = _CommReadByte(1)

WEnd

Read the two bytes sent by the Serial.println, YOU MUST DELETE THESE TWO LINES IF you modify the code. It’s redundant anyway because of the synchronization byte technique I used.

WEnd ends the main loop

Func quit()

Exit

EndFunc

The quit function exits the Autoit script.

Posted in Arduino, Hacks, Microcontroller, Projects.