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 SettingsAdministratorMy DocumentsArduinonunchuckmousearduinoi2c.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] = 0×40;
data[1] = 0×00;
twi_writeTo(0×52, 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 0×40 and the second is 0×00, these two will be sent to the nunchuck in order when twi_writeTo is called.
When calling twi_writeTo 0×52 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(0×52, 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 0×52 to read 6 bytes. Then the for loop places the data from the buffer into the data array.
// decipher data _joyX = (data[0] ^ 0×17) + 0×17; _joyY = (data[1] ^ 0×17) + 0×17; _accelX = (data[2] ^ 0×17) + 0×17; _accelY = (data[3] ^ 0×17) + 0×17; _accelZ = (data[4] ^ 0×17) + 0×17; buttons = (data[5] ^ 0×17) + 0×17;
Raw data being decrypted, simple operation, first XOR byte by 0×17, then add 0×17 to it, since these are all 8 bit unsigned byte integers, adding the 0×17 will not cause problems if it goes over 255.
// get ready for next request data[0] = 0×00; twi_writeTo(0×52, 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, 0×48, 0xB0); xAngle = accelToAngle(accelX); accelY = chuckProc(_accelY, 0×46, 0xAF); yAngle = accelToAngle(accelY); accelZ = chuckProc(_accelZ, 0×4A, 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.


Stumble it!






