Help Parsing data from Wii IMU sensors

I'm working toward a quadcopter project but as a first step I'm looking to build a two-wheeled balancing-style robot.

A lot of the work with interfacing the combination of the Motion Plus and Nunchuk sensors has been done on the forums here: http://arduino.cc/forum/index.php/topic,8544.0.html, but I'm looking to redo the parseData() function in order to get the data output in something that is more meaningful.

The eventual goal is to turn the raw accelerometer values into G readings, and the gyroscope into deg/sec rate readings, to eventually use them in a complementary filter.

Here are some pictures of the setup I'm using: an original Wii MP extension board and a knockoff nunchuck board. Both are mounted and soldered together, powered by a 3.3v regulator.

Images hosted on Box.com but I can't embed them directly, so here are the links:
https://www.box.com/s/lpao736l5yh7md0ab9tk
https://www.box.com/s/q84y91g8me4fp549d6wd
https://www.box.com/s/g37pihrz38v4pldgef8t

The last image is the breadboard held at a 45-degree angle for testing the calibration.

Right now the problem I'm encountering is the fact that the values output do not correlate linearly to the acceleration they should be reading. My code generates a +/- 1000 range for each accelerometer axis. When held at 45-degrees, rather than reading 500, I am getting closer to 650.

EDIT: I forgot my basic trig and a reading closer to 700 could correspond to 45 degrees, so I guess there is nothing wrong...nevermind

Are accelerometers normally non-linear, or should I be looking to use some function besides direct map() to calculate G readings?

Finally, here is the current code I'm working with, with my modifications. I am not the best with C, so please feel free to correct any bad programming practices.

// .......................................................................
// Code for reading data from a Wii Motion plus device connected to a Nunchuck
// Links to other sites
// http://www.windmeadow.com/node/42   .... first time i saw arduino nunchuk interface
// http://www.kako.com/neta/2009-017/2009-017.html# .....Japanese site with lots of Wii info
// http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Wii_Motion_Plus    .... the one and only
// http://randomhacksofboredom.blogspot.com/2009/06/wii-motion-plus-arduino-love.html
// ....original motion plus but not passthrough
// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248889032/35   .... great Kalman filter code
// thanks to duckhead and knuckles904. I will be using that code for sure.
// http://obex.parallax.com/objects/471/   .... ideas for passthrough
// by Krulkip
// Here is the bitmapping which is changed from the standard nunchuk mapping
// In Nunchuk mode:
//          Bit
// Byte     7       6       5       4       3       2       1       0
// 0       SX<7-----------------------------------------------------0>
// 1       SY<7-----------------------------------------------------0>
// 2       AX<9-----------------------------------------------------2>
// 3       AY<9-----------------------------------------------------2>
// 4       AZ<9---------------------------------------------3>      1
// 5       AZ<2-----1>     AY<1>   AX<1>   BC      BZ       0       0
// Please note also the loss of the bit0 resolution.
// Byte 5 Bit 0 and 1 is used for nunchuk (0 0) = nunchuk or motion plus  (1 0) is motion plus detection.
// Byte 4 Bit 0 which is the extention controller detection bit. (1 = extension present)
// Hardware Arduino with ATMega 168
// Connections SCA to AD4 (Analog4) and SCL to AD5 (Analog5)
/*      
 Constants for scaling accelerometer values: Example of the accelerometer rotation values for y axis on Ardvark's nunchuk.
 
 Exact values differ between x and y axis, and probably between nunchuks, but the range value may be constant:
 
 298 ayMIN
 _|_
 /     \
 ayMID 513 - |       | - 513 ayMID
 \ _ _ / 
 |
 728 ayMAX
 
 ayRange = (ayMID - ayMIN)*2 = ayMAX - ayMIN = 430
 */
//=========================================================
// changelog
// ---------
// 8/9/2009 - by dimkasta @ www.kastaniotis.net
// Adapted for use with the transistor-less switching code originally written by krulkip on 
// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248889032/60
// Calibration and utility code based on the code by duckhead
// 9/9/2009 - by dimkasta @ www.kastaniotis.net
// Added Accelerometer calibration
// Removed the casting to double and the multiplying with millis/sec from the cycle saving to further greately 
// increase the free time to ~1/50 in comparison to the reading time.
// 18/9/2009 - by Ardvark
// Slow/fast bit reading fixed: Slow degree/sec scale value appears correct.
// Included details for absolute scaling of accelerometer values.
// Included complementary filter for combining single accelerometer/gyro axes to produce drift- and translation-suppressed orientation values.
//=========================================================
// TODO LIST - Known issues
// ---------
// Assign common names to gyro and accel axes
//........................................................................

#include <Wire.h>

unsigned long Now = millis();
unsigned long lastread = Now;
unsigned long lastreadMP = Now;
//The system clock function millis() returns a value in milliseconds.  We need it in seconds.
//Instead of dividing by 1000 we multiply by 0.001 for performance reasons.

boolean debug = true;

boolean calibrating = false;

uint8_t data[6];            // array to store arduino output
int cnt = 0;
int ledPin = 13;
int xID;

//wiibrew info seems to be incorrect with regards to scaling, see instead Xevel's comments: http://www.assembla.com/wiki/show/alicewiimotionplus/slow_and_fast_modes
static const int wmpSlowToDegreePerSec = 16; //when gyro speed 1, 16 units = 1°/s
static const int wmpFastToDegreePerSec = 4; //when gyro speed 0, 4 units = 0,25°/s

// Hardware identifiers. Polling data[5]&0x03 for present device
static const int NC = 0x00;
static const int MP = 0x02;
int currentDevice;

// WM+ state variables - if true, then in slow (high res) mode
boolean slowYaw = 0;
boolean slowPitch = 0;
boolean slowRoll = 0;

int rollScale;
int pitchScale;
int yawScale;

// Calibrated gyro values
int yaw = 0;
int pitch = 0;
int roll = 0;

float dt; // msec
float dtMP; // this is the time elapsed between readings of the motion plus (MP)

float rollCF = 0;
float pitchCF = 0;

static const double DegreeToRadian = 0.0174532925;  // PI/180

// Complimentary filter: inspired by Shane Colton's description in filter.pdf at http://web.mit.edu/first/segway/segspecs.zip

// Tweak tau to alter the performance of the complimentary filter.
// Increase tau: influence of the gyro input increases (less translation artifacts). 
// Reduce tau: influence of the accelerometer input increases (faster drift-correction).
// This filtering is temporal: translations that occur over a time scale shorter than tau will be suppressed,
// and gyro changes that occur over a time scale longer than tau will have their drift corrected.
// NB: changes in the read rate of the gyro (dtMP) will change the filter's performance (change tau to compensate).

static const float tau = 0.95; // unit: seconds. 
float compFilter(float prevValue, int gyro, double accel, float timeStep) {
  float a = tau/(tau + dtMP);
  float b = 1 - a;
  return (float) (a*(prevValue + ((gyro*DegreeToRadian)*timeStep)) + (b*accel));
}

// raw accelerometer values, no units
int ax_r = 0;
int ay_r = 0;
int az_r = 0;

// adjusted accelerometer readings, in G
float ax_g = 0;
float ay_g = 0;
float az_g = 0;

// accelerometer ranges, different per NK, experiment with raw values to find values for your own
// each max and min correspond to 1G and -1G for each axis, used to map() to +1 to -1 G for CF
#define ax_min 202
#define ax_max 570
#define ay_min 204
#define ay_max 570
#define az_min 184
#define az_max 566

// gyro calibration zeros
int yaw0 = 0;
int pitch0 = 0;
int roll0 = 0;

void setup ()
{
  Serial.begin (115200);
  Wire.begin();
  initializeSensors();
  calibrateZeroes();
}

void send_zero ()
{
  Wire.beginTransmission(0x52);
  Wire.write(0x00);
  Wire.endTransmission();
}

void loop ()
{
  Now = millis();
  dt = Now - lastread; //compute the time delta since last iteration, in msec.
  if (dt >= 10) //Only process delta angles if at least 1/100 of a second has elapsed. NB: 9 ms is the lowest possible interval before things jam up on an ATmega 328
  {
    readData();
    lastread = Now; 
  }
  else // just for demo of the free cycles
  {
  /*Serial.print("ax_r: ");
    Serial.print(ax_r);
    Serial.print(" ay_r: ");
    Serial.print(ay_r);
    Serial.print(" az_r: ");
    Serial.println(az_r);  */
    Serial.print(" ax_g: ");
    Serial.print(ax_g);
    Serial.print(" ay_g: ");
    Serial.print(ay_g);
    Serial.print(" az_g: ");
    Serial.println(az_g);  
    delay(50);
  }
}

// split to fit in post

And the second half of the code, which has the actual parsing function:

void readData()
{  
  long startReading = millis();
  //digitalWrite(13,HIGH);  // first flash the LED to show activity

  send_zero (); // send the request for next bytes
  //delay (10);

  // now follows conversion command instructing extension controller to get
  // all sensor data and put them into 6 byte register within the extension controller
  Wire.requestFrom (0x52,6);
  data[0] = Wire.read();
  data[1] = Wire.read();
  data[2] = Wire.read();
  data[3] = Wire.read();
  data[4] = Wire.read();
  data[5] = Wire.read();

  parseData();
  // digitalWrite(13,LOW);  // first flash the LED to show activity 
}

void parseData ()
{
  // check if nunchuk is really connected
  if ((data[4]&0x01)==0x01)
  {
    //printLine("Ext con: ");
    currentDevice = data[5]&0x03;
  }
  if (currentDevice == NC)
  {
    // Dimitris version of accl readings
    ax_r = (data[2] << 2) + ((data[5] >> 3) & 2) ;
    ay_r = (data[3] << 2) + ((data[5] >> 4) & 2);
    az_r = (data[4] << 2) + ((data[5] >> 5) & 6) ;

    ax_g = map(ax_r, ax_min, ax_max, 1000, -1000); // map accelerometer output to +/- 100, then multiply by 0.01 to get +/- 1G
    ay_g = map(ay_r, ay_min, ay_max, -1000, 1000);
    az_g = map(az_r, az_min, az_max, 1000, -1000);
  }
  else if  (currentDevice == MP) 
  {
    dtMP = (Now - lastreadMP)*0.001; //compute the time delta since last MP read, in sec.
    lastreadMP = Now;

    roll = (((data[4]&0xFC)<<6) + data[1]);
    pitch = (((data[5]&0xFC)<<6) + data[0]);
    yaw = (((data[3]&0xFC)<<6) + data[2]);

    if(calibrating == false) //Dont compensate if we are doing the calibration measurements.
    {

      slowPitch = (data[4]&0x02)>>1;
      slowRoll = (data[3]&0x01)>>0;
      slowYaw = (data[3]&0x02)>>1;

      rollScale  = slowRoll  ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec; // if speed == 1, then wmpSlowToDegreePerSec, else wmpFastToDegreePerSec
      pitchScale = slowPitch ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec;
      yawScale   = slowYaw   ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec;

      //Calibrating 
      roll -= roll0;
      pitch -= pitch0;
      yaw -= yaw0;

      roll = roll/rollScale;    // deg/s = mV/(mV/deg/s)
      pitch = pitch/pitchScale; 
      yaw = yaw/yawScale; 

      // Complimentary filter: inspired by Shane Colton's description in filter.pdf at http://web.mit.edu/first/segway/segspecs.zip
      //rollCF = compFilter(rollCF, roll, x, dtMP);
      //pitchCF = compFilter(pitchCF, pitch, y, dtMP);

    }
  }
}

void initializeSensors()
{

  digitalWrite(13,HIGH);  // first flash the LED to show activity
  Serial.println ("Now detecting WM+");
  //delay(100);
  // now make Wii Motion plus the active extension
  // nunchuk mode = 05, wm+ only = 04, classic controller = 07
  Serial.println ("Activating WM+ in NC mode (5)...");
  Wire.beginTransmission(0x53);
  Wire.write(0xFE);
  Wire.write(0x05);// nunchuk
  Wire.endTransmission();
  Serial.println (" OK done");
  //delay (100);
  // now innitiate Wii Motion plus
  Serial.println ("Innitialising Wii Motion plus ........");
  Wire.beginTransmission(0x53);
  Wire.write(0xF0);
  Wire.write(0x55);
  Wire.endTransmission();
  Serial.println(" OK done");
  //delay (100);
  Serial.println ("Reading address at 0xFA .......");
  Wire.beginTransmission(0x52);
  Wire.write(0xFA);
  Wire.endTransmission();
  Serial.println(" OK done");
  //delay (100);
  Wire.requestFrom (0x52,6);
  data[0] = Wire.read();//Serial.print(outbuf[0],HEX);Serial.print(" ");
  data[1] = Wire.read();//Serial.print(outbuf[1],HEX);Serial.print(" ");
  data[2] = Wire.read();//Serial.print(outbuf[2],HEX);Serial.print(" ");
  data[3] = Wire.read();//Serial.print(outbuf[3],HEX);Serial.print(" ");
  data[4] = Wire.read();//Serial.print(outbuf[4],HEX);Serial.print(" ");
  data[5] = Wire.read();//Serial.print(outbuf[5],HEX);Serial.print(" ");

  xID= data[0] + data[1] + data[2] + data[3] + data[4] + data[5];
  Serial.println("Extension controller xID = 0x");
  Serial.print(xID,HEX);
  if (xID == 0xCB) { 
    Serial.println ("MP+ on but not active"); 
  }
  if (xID == 0xCE) { 
    Serial.println ("MP+ on and active"); 
  }
  if (xID == 0x00) { 
    Serial.println ("MP+ not on"); 
  }
  //delay (100);
  // Now we want to point the read adress to 0xa40008 where the 6 byte data is stored
  Serial.println ("Reading address at 0x08 .........");
  Wire.beginTransmission(0x52);
  Wire.write(0x08);
  Wire.endTransmission();
  Serial.println(" OK done");
  Serial.println ("Finished setup");

  digitalWrite(13,LOW); // Led off
}

/*
 * Find the steady-state of the WM+ gyros.  This function reads the gyro values 100 times and averages them.
 * Those values are used as the baseline steady-state for all subsequent reads.  As a result it is very
 * important that the device remain relatively still during startup.
 */


void calibrateZeroes()
{  // wiibrew: While the Wiimote is still, the values will be about 0x1F7F (8,063)
  calibrating = true;  
  if(debug)
  {
    Serial.println("Starting calibration...");
  }
  //Gyros
  long y0 = 0;
  long p0 = 0;
  long r0 = 0;

  const int avg = 100; //100 reads of the gyros

  digitalWrite(13,HIGH);  // first flash the LED to show activity

  for (int i=0;i<avg; i++)
  {
    delay(10); // bypasses the normal global delays 
    readData();

    if(currentDevice == MP)
    {
      y0+=yaw;
      r0+=roll;
      p0+=pitch;
    }
  }

  yaw0 = y0/(avg);
  roll0 = r0/(avg);
  pitch0 = p0/(avg);

  calibrating = false;

  digitalWrite(13,LOW); // Led off
  if(debug)
  {
    Serial.println("Calibration results...");
    Serial.print("Yaw0:");
    Serial.println(yaw0);
    Serial.print("Roll0:");
    Serial.println(roll0);
    Serial.print("Pitch0:");
    Serial.println(pitch0);
  }
}