Wii Classic Controller Protocol

As part of some of the junk I have built, I have needed to know how some game controllers communicate with their consoles. In this case, I found out that the Wii Classic controller or Nunchuck is capable of using more than one data format for some reason.

The data format that you will find on the internet is as follows –

 	Bit
Byte	7	6	5	4	3	2	1	0
0	RX<4:3>	LX<5:0>
1	RX<2:1>	LY<5:0>
2	RX<0>	LT<4:3>	RY<4:0>
3	LT<2:0>	RT<4:0>
4	BDR	BDD	BLT	B-	BH	B+	BRT	1
5	BZL	BB	BY	BA	BX	BZR	BDL	BDU

For some unknown reason, the joystick data is split up over multiple bytes, I can’t think of any sane reason for doing this at all.

However, what I discovered when attempting the create a controller adaptor for a NES Mini console, is that there is at least one more format that can be used.

byte 1 - Stick LX
byte 2 - Stick RX
byte 3 - Stick LY
byte 4 - Stick RY

byte 5 - unknown
byte 6 - unknown

byte 7 - 1 - Dpad Right
    2 - Dpad Down
    3 - Left Shoulder
    4 - Minus/Select
    5 - Home
    6 - Plus/Start
    7 - Right Shoulder
    8 - unknown

byte 8 - 1 - Left Trigger
    2 - B
    3 - Y
    4 - A
    5 - X
    6 - Right Trigger
    7 - Dpad Left
    8 - Dpad Up

 

Using the above information and a little experimentation, I came up with the following Arduino code to read a Wii classic controller

Wii_Controller_3.h

class WiiController
{
  public:
    int error;
    int oldError;
    unsigned char dataFormat;
    unsigned char controllerType[6];
    bool connected;

    unsigned char conTypes[3][6]={
      {0x00,0x00,0xA4,0x20,0x00,0x00}, // 0 = Nunchuck
      {0x00,0x00,0xA4,0x20,0x01,0x01}, // 1 = Classic controller
      {0x01,0x00,0xA4,0x20,0x01,0x01}, // 2 = Classic controller pro
    };

    unsigned char rx,ry,lx,ly,rt,lt;
    bool a,b,x,y,minus,home,plus,l,r,zl,zr;
    bool up,down,left,right;

    void init();
    void update();

  private:
    void _sendByte(byte data, byte location);
    void _write_zero();
    int ADDRESS = 0x52;
};

void padprint(int number, int chars) {
 for (int i = pow(10, chars - 1); number <= i - 1; i /= 10) Serial.print("0");
 Serial.print(number);
}

void WiiController::init()
{
  
  Wire.begin();
  WiiController::_sendByte(0x55, 0xF0); // init controller
  WiiController::_sendByte(0x00, 0xFB);
  WiiController::_sendByte(0x03, 0xFE); // request datatype 3

  // check to see if the format is selected
  Wire.beginTransmission(WiiController::ADDRESS);      // transmit to device (byte)0x52
  Wire.write((byte)0xFA);           // writes one byte
  Wire.endTransmission();    // stop transmitting
  delay(10);
  Wire.requestFrom(WiiController::ADDRESS, 6);
  char t=0;
  while(Wire.available()){
    controllerType[t++] = Wire.read();
  }
  dataFormat = controllerType[4];
    WiiController::connected=1;

/*
  Known data types
  1. Standard Wii Classic controller
  2. ?
  3. Classic controller, easier to read, not universally supported by 3rd part controllers
  4. Wii Motion Plus
*/
  WiiController::update();
}

void WiiController::update()
{
  int count = 0;
  int values[8];
  Wire.beginTransmission(WiiController::ADDRESS);
  oldError=error;
  error=Wire.endTransmission();
  // if previous read was an error and current read is not, then we probably plugged on a controller, so init it.
  if(error==0 && oldError!=0){WiiController::init();}
  if(error!=0){
    WiiController::connected=0;
    // if failed to connect, reset controller type
    for(char t=0; t<6; t++){
      controllerType[t] = 0x00;
    }
  }

 Wire.requestFrom(WiiController::ADDRESS, 8);
  while(Wire.available()){
    values[count] = Wire.read();
    count++;
  }

  if(dataFormat==3){ // NES Classic format
    WiiController::lx = values[0];
    WiiController::rx = values[1];
    WiiController::ly = values[2];
    WiiController::ry = values[3];
    WiiController::lt = values[4];
    WiiController::rt = values[5];

    WiiController::right  = values[7]&B10000000 ? 0:1;
    WiiController::down   = values[7]&B01000000 ? 0:1;
    WiiController::l      = values[7]&B00100000 ? 0:1;
    WiiController::minus  = values[7]&B00010000 ? 0:1;
    WiiController::home   = values[7]&B00001000 ? 0:1;
    WiiController::plus   = values[7]&B00000100 ? 0:1;
    WiiController::r      = values[7]&B00000010 ? 0:1;
    // I don't know what, if anything the last bit represents
    WiiController::zl     = values[8]&B10000000 ? 0:1;
    WiiController::b      = values[8]&B01000000 ? 0:1;
    WiiController::y      = values[8]&B00100000 ? 0:1;
    WiiController::a      = values[8]&B00010000 ? 0:1;
    WiiController::x      = values[8]&B00001000 ? 0:1;
    WiiController::rt     = values[8]&B00000100 ? 0:1;
    WiiController::up     = values[8]&B00000010 ? 0:1;
    WiiController::down   = values[8]&B00000001 ? 0:1;
  }
  
  WiiController::_write_zero();
}

void WiiController::_sendByte(byte data, byte location)
{
  Wire.beginTransmission(WiiController::ADDRESS);
  Wire.write(location);
  Wire.write(data);
  Wire.endTransmission();
  delay(10);
}

void WiiController::_write_zero(){
  Wire.beginTransmission(WiiController::ADDRESS);      // transmit to device (byte)0x52
  Wire.write((byte)0x00);           // writes one byte
  Wire.endTransmission();    // stop transmitting
}

 

Wii_Controller_3.ino

#include <Wire.h>
#define BAUDRATE 9600

#include "Wii_Controller_3.h"

WiiController classicCon = WiiController();

char *controller[3] = {"Nunchuck","Classic controller","Classic controller pro"};
bool oldConnection=0;


void checkController(){
  Serial.print("Controller data format:");
  Serial.println(classicCon.dataFormat);

  char controllerType=-1;
  for(char s=0; s<3; s++){
    unsigned char tempCount=0;
    for(char t=0; t<6; t++){
      // check controller against known models. 5th byte ignored, as it is changed by init process
      if(t!=4){ if(classicCon.controllerType[t] == classicCon.conTypes[s][t]){ tempCount++; } }
    }
    if(tempCount==5){controllerType=s;}
  }
  Serial.print("Controller:");
  if(controllerType<0){
    Serial.println("Not Connected");
  }else{
    Serial.println(controller[controllerType]);
  }
  
}

void setup()
{
  Serial.begin(BAUDRATE);
  while(!Serial){};
  classicCon.init();
}

void loop()
{
 oldConnection = classicCon.connected;
 classicCon.update();
 if(classicCon.connected!=oldConnection){
  checkController(); 
 }

}

There are four different communication modes for Wii classic controllers,

  1. Default – used on the Wii/WiiU
  2. Unknown
  3. Better format, used by NES/SNES mini
  4. Used by Wii Motion Plus adaptor.