PART 10: Wii motion plus + Nunchuck (Kalman Filter)

我知道這已經過時很久了,
但是我蹺課太久了,現在捕回來@@
找到下列程式有一個重點,就是經過了 "Kalman Filter"的計算,
這樣才能夠實際應用, 不然一堆雜訊是沒辦法用的.
但是如果要更好的效果, 就要選用 9DOF IMU,
搭配Compass一起計算...這是後面的課題了..


// Modified Kalman code using the Y-Axis from a Wii MotionPlus and the X and Z from a Nunchuck.
// Nunchuck joystick provides corrections - X for Gyro, Y for Accelerometer
// Also uses "new" style Init to provide unencrypted data from the Nunchuck to avoid the XOR on each byte.
// Requires the WM+ and Nunchuck to be connected to Arduino pins D3/D4 using the schematic from Knuckles904
// See http://randomhacksofboredom.blogspot.com/2009/07/motion-plus-and-nunchuck-together-on.html
// Kalman Code Originally By Jordi Munoz... //Si deseas la version en Espanol, no dudes en escribirme...
//
// Altered by Adrian Carter <adrian@canberrariders.org.au> to work with WiiM+ and Nunchuck

#include <math.h>
#include <Wire.h>
/////////////////////////////////////////
#define NUMREADINGS 5 //Gyro noise filter
// test for 3 axis
int readingsX[NUMREADINGS];
int readingsY[NUMREADINGS];
int readingsZ[NUMREADINGS];                    // the readings
int totalX = 0;
int totalY = 0;
int totalZ = 0;                                   // the running total
int averageX = 0;                                  // the average
int averageY = 0;
int averageZ = 0;
int lastaverageZ =0;
// End of 3 axis stuff
int readings[NUMREADINGS];                    // the readings from the analog input (gyro)
int index = 0;                                    // the index of the current reading
int total = 0;                                    // the running total
int average = 0;                                  // the average
int inputPin =0;                                  // Gyro Analog input
//WM+ stuff
byte data[6]; //six data bytes
int yaw, pitch, roll; //three axes
int yaw0, pitch0, roll0; //calibration zeroes
///////////////////////////////////////

float        dt        = .06; //( 1024.0 * 256.0 ) / 16000000.0; (Kalman)
int mydt = 20; //in ms.
static float                P[2][2] = { //(Kalman)

  {
    1, 0   }
  , //(Kalman)
  {
    0, 1   }
  ,//(Kalman)
}; //(Kalman)
// extra 2 axis..
static float                P1[2][2] = {  { 1, 0 } ,  { 0, 1 } ,};
static float                P2[2][2] = {  { 1, 0 } ,  { 0, 1 } ,};
static float                P3[2][2] = {  { 1, 0 } ,  { 0, 1 } ,}; //(Kalman)


/*
 * Our two states, the angle and the gyro bias.  As a byproduct of computing
 * the angle, we also have an unbiased angular rate available.   These are
 * read-only to the user of the module.
 */
float                        angle; //(Kalman)
float                         angleX;
float                         angleY;
float                         angleZ;
float                        q_bias; //(Kalman)
float                         q_bias_X;
float                         q_bias_Y;
float                         q_bias_Z;
float                         rate_X;
float                         rate_Y;
float                         rate_Z;
float                         q_m_X; //(Kalman)
float                         q_m_Y;
float                         q_m_Z;
float                         rotationZ;
float                        rate; //(Kalman)
float                         q_m; //(Kalman)

// adjusts
int gyro_adjust = 1;
int accel_adjust = 128;

int joy_x_axis = 0; //NunChuck Joysticks potentiometers
int joy_y_axis = 0;
int ax_m=0;        //NunChuck X acceleration
// Re-added Y Axis
int ay_m=0;        //NunChuck Y acceleration
int az_m=0;        //NunChuck Z acceleration
byte outbuf[6];                // array to store arduino output
int cnt = 0;                //Counter
unsigned long lastread=0;

/*
 * R represents the measurement covariance noise.  In this case,
 * it is a 1x1 matrix that says that we expect 0.3 rad jitter
 * from the accelerometer.
 */
float        R_angle        = .3; //.3 deafault, but is adjusted externally (Kalman)


/*
 * Q is a 2x2 matrix that represents the process covariance noise.
 * In this case, it indicates how much we trust the acceleromter
 * relative to the gyros.
 */
static const float        Q_angle        = 0.001; //(Kalman)
static const float        Q_gyro        = 0.003; //(Kalman)

void switchtonunchuck(){
/*  PORTE ^= 32; // Toggle D3 LOW
  PORTG |= 32; // Force D4 HIGH */ // Fast switching of pins... Arduino MEGA specific...
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(4, HIGH);

}

void switchtowmp(){
/*  PORTG ^= 32; // Toggle D4 LOW
  PORTE |= 32; // Force D3 HIGH */ // Fast switching of pins... Arduino MEGA Specific pin maps, so switched back to 'normal' code for example
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(3, HIGH);
}

void nunchuck_init () //(nunchuck)
{
// Uses New style init - no longer encrypted so no need to XOR bytes later... might save some cycles
 Wire.beginTransmission (0x52);        // transmit to device 0x52
  Wire.write (0xF0);                // sends memory address
  Wire.write (0x55);                // sends sent a zero.
  Wire.endTransmission ();        // stop transmitting
  delay(100);
  Wire.beginTransmission (0x52);        // transmit to device 0x52
  Wire.write (0xFB);                // sends memory address
  Wire.write (0x00);                // sends sent a zero.
  Wire.endTransmission ();        // stop transmitting
}

void setup()
{
  Serial.begin(115200);
/*  DDRG |= 32; // Force D3 to Output // Arduino MEGA "fast" pin config. Reverted to 'normal' for example post.
  DDRE |= 32; // Force D4 to Output
  PORTG ^= 32; // Toggle D3 HIGH
  PORTE ^= 32; // Toggle D4 HIGH */

  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);

  Wire.begin ();                // join i2c bus with address 0x52 (nunchuck)
  switchtowmp();
  wmpOn();
  calibrateZeroes();
  switchtonunchuck();
  nunchuck_init (); // send the initilization handshake

  for (int i = 0; i < NUMREADINGS; i++)
    readings[i] = 0;                            // initialize all the readings to 0 (gyro average filter)
}

void send_zero () //(nunchuck)
{
  Wire.beginTransmission (0x52);        // transmit to device 0x52 (nunchuck)
  Wire.write (0x00);                // sends one byte (nunchuck)
  Wire.endTransmission ();        // stop transmitting (nunchuck)
}

void wmpOn(){
Wire.beginTransmission(0x53);//WM+ starts out deactivated at address 0x53
Wire.write(0xfe); //send 0x04 to address 0xFE to activate WM+
Wire.write(0x04);
Wire.endTransmission(); //WM+ jumps to address 0x52 and is now active
}

void wmpOff(){
Wire.beginTransmission(82);
Wire.write(0xf0);//address then
Wire.write(0x55);//command
Wire.endTransmission();
}

void wmpSendZero(){
Wire.beginTransmission(0x52);//now at address 0x52
Wire.write(0x00); //send zero to signal we want info
Wire.endTransmission();
}

void calibrateZeroes(){
for (int i=0;i<10;i++){
wmpSendZero();
Wire.requestFrom(0x52,6);
for (int i=0;i<6;i++){
data[i]=Wire.read();
}
yaw0+=(((data[3] >> 2) << 8)+data[0])/10;//average 10 readings
pitch0+=(((data[4] >> 2) << 8)+data[1])/10;// for each zero
roll0+=(((data[5] >> 2) << 8)+data[2])/10;
}
}

void receiveData(){
wmpSendZero(); //send zero before each request (same as nunchuck)
Wire.requestFrom(0x52,6); //request the six bytes from the WM+
for (int i=0;i<6;i++){
data[i]=Wire.read();
}
//see http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Wii_Motion_Plus
//for info on what each byte represents
yaw=((data[3] >> 2) << 8)+data[0]-yaw0;
pitch=((data[4] >> 2) << 8)+data[1]-pitch0;
roll=((data[5] >> 2) << 8)+data[2]-roll0;
}

void receiveRaw(){
wmpSendZero(); //send zero before each request (same as nunchuck)
Wire.requestFrom(0x52,6);//request the six bytes from the WM+
for (int i=0;i<6;i++){
data[i]=Wire.read();
}
yaw=((data[3] >> 2) << 8)+data[0];
pitch=((data[4] >> 2) << 8)+data[1];
roll=((data[5] >> 2) << 8)+data[2];
}


void loop()
{
  if((millis()-lastread) >= mydt) { // sample every dt ms -> 1000/dt hz.
    lastread = millis();
    total += readings[index];
    totalX += readingsX[index];
    totalY += readingsY[index];
    totalZ += readingsZ[index];                   // add the reading to the total
    switchtowmp();
    receiveData();
    switchtonunchuck();
    readings[index] = int(pitch/20); // read from the gyro sensor (/20 to get degree/second) pitch == roll
    readingsX[index] = int(yaw/20); // read from the gyro sensor (/20 to get degree/second) pitch == roll
//Continued
    readingsX[index] = int(roll/20); // read from the gyro sensor (/20 to get degree/second) pitch == roll
    readingsY[index] = int(pitch/20); // read from the gyro sensor (/20 to get degree/second) pitch == roll
    readingsZ[index] = int(yaw/20); // read from the gyro sensor (/20 to get degree/second) pitch == roll
//    receiveRaw(); // Provides unbiased output from WM+ (i.e, without calibrationZeros)


    index = (index + 1);                          // advance to the next index

    if (index >= NUMREADINGS)                   // if we're at the end of the array...
        index = 0;                                    // ...wrap around to the beginning

    average = (total / NUMREADINGS) - 511;
    averageX = (totalX / NUMREADINGS) - 511;
    averageY = (totalY / NUMREADINGS) - 511;
    averageZ = (totalZ / NUMREADINGS) - 511;    // calculate the average of gyro input

    q_m=((average*(joy_x_axis*0.00392156862))/(180/PI)); //GYRO convertion to Radian and external correction with nunchuk joystick
    q_m_X=((averageX*(joy_x_axis*0.00392156862))/(180/PI)); //GYRO convertion to Radian and external correction with nunchuk joystick
    q_m_Y=((averageY*(joy_x_axis*0.00392156862))/(180/PI)); //GYRO convertion to Radian and external correction with nunchuk joystick
    q_m_Z=((averageZ*(joy_x_axis*0.00392156862))/(180/PI)); //GYRO convertion to Radian and external correction with nunchuk joystick

    /* Unbias our gyro */
    const float        q_X = q_m_X - q_bias_X; //(Kalman)
    const float q_Y = q_m_Y - q_bias_Y;
    const float q_Z = q_m_Z - q_bias_Z;
    const float        q = q_m - q_bias; //(Kalman)

    const float                Pdot[2 * 2] = {
        Q_angle - P[0][1] - P[1][0],        /* 0,0 */ //(Kalman)
        - P[1][1],                /* 0,1 */
        - P[1][1],                /* 1,0 */
        Q_gyro                                /* 1,1 */
    };

     const float        PdotX[2 * 2] = {
        Q_angle - P1[0][1] - P1[1][0],        /* 0,0 */ //(Kalman)
        - P1[1][1],                          /* 0,1 */
        - P1[1][1],                          /* 1,0 */
        Q_gyro                                /* 1,1 */
    };

    const float        PdotY[2 * 2] = {
        Q_angle - P2[0][1] - P2[1][0],        /* 0,0 */ //(Kalman)
        - P2[1][1],                          /* 0,1 */
        - P2[1][1],                          /* 1,0 */
        Q_gyro                                /* 1,1 */
    };

    const float        PdotZ[2 * 2] = {
        Q_angle - P3[0][1] - P3[1][0],        /* 0,0 */ //(Kalman)
        - P3[1][1],                          /* 0,1 */
        - P3[1][1],                          /* 1,0 */
        Q_gyro                                /* 1,1 */
    };

    /* Store our unbias gyro estimate */
    rate = q; //(Kalman)
    rate_X = q_X; //(Kalman)
    rate_Y = q_Y;
    rate_Z = q_Z;

    /*
         * Update our angle estimate
              * angle += angle_dot * dt
              *         += (gyro - gyro_bias) * dt
              *         += q * dt
              */
    angle += q * dt; //(Kalman)
    angleX += q_X * dt; //(Kalman)
    angleY += q_Y * dt;
    angleZ += q_Z * dt;

    /* Update the covariance matrix */
    P[0][0] += Pdot[0] * dt; //(Kalman)
    P[0][1] += Pdot[1] * dt; //(Kalman)
    P[1][0] += Pdot[2] * dt; //(Kalman)
    P[1][1] += Pdot[3] * dt; //(Kalman)

    P1[0][0] += PdotX[0] * dt; //(Kalman) X axis
    P1[0][1] += PdotX[1] * dt; //(Kalman)
    P1[1][0] += PdotX[2] * dt; //(Kalman)
    P1[1][1] += PdotX[3] * dt; //(Kalman)

    P2[0][0] += PdotY[0] * dt;//y axis
    P2[0][1] += PdotY[1] * dt;
    P2[1][0] += PdotY[2] * dt;
    P2[1][1] += PdotY[3] * dt;

    P3[0][0] += PdotZ[0] * dt;//y axis
    P3[0][1] += PdotZ[1] * dt;
    P3[1][0] += PdotZ[2] * dt;
    P3[1][1] += PdotZ[3] * dt;

    Wire.requestFrom (0x52, 6);        // request data from nunchuck

    while (Wire.available ()) //(NunChuck)
    {
        outbuf[cnt] = Wire.read ();        // receive byte as an integer //(NunChuck)
        cnt++;//(NunChuck)
    }

    send_zero (); // send the request for next bytes
    // If we recieved the 6 bytes, then print them //(NunChuck)
    if (cnt >= 5) //(NunChuck)
    {
        print (); //(NunChuck)
    }
    cnt = 0; //(NunChuck)

    R_angle= (joy_y_axis+1)*0.0098039; //external adjust jitter of accelerometer with nunchuck joystick


    const float                angle_m = atan2( ax_m, az_m ); //(Kalman)
    const float                angle_err = angle_m - angle; //(Kalman)
    const float                C_0 = 1; //(Kalman)
    const float                PCt_0 = C_0 * P[0][0];  //(Kalman)
    const float                PCt_1 = C_0 * P[1][0]; //(Kalman)
    const float                E =R_angle+ C_0 * PCt_0; //(Kalman)
    const float                K_0 = PCt_0 / E; //(Kalman)
    const float                K_1 = PCt_1 / E; //(Kalman)
    const float                t_0 = PCt_0; /* C_0 * P[0][0] + C_1 * P[1][0] (Kalman) */

    const float                t_1 = C_0 * P[0][1]; /* + C_1 * P[1][1]  = 0 (Kalman) */


    P[0][0] -= K_0 * t_0; //(Kalman)
    P[0][1] -= K_0 * t_1; //(Kalman)
    P[1][0] -= K_1 * t_0; //(Kalman)
    P[1][1] -= K_1 * t_1; //(Kalman)
    angle        += K_0 * angle_err; //(Kalman)
    q_bias        += K_1 * angle_err; //(Kalman)

          const float                angle_mX = atan2( ax_m, az_m ); //(Kalman) X axis
    const float                angle_errX = angle_mX - angleX; //(Kalman) X axis
    const float                C_0_1 = 1; //(Kalman) X axis
    const float                PCt_0_1 = C_0_1 * P[0][0];  //(Kalman)
    const float                PCt_1_1 = C_0_1 * P[1][0]; //(Kalman)
    const float                E_1 =R_angle+ C_0_1 * PCt_0_1; //(Kalman)
    const float                K_0_1 = PCt_0_1 / E_1; //(Kalman)
    const float                K_1_1 = PCt_1_1 / E_1; //(Kalman)
    const float                t_0_1 = PCt_0_1; /* C_0_1 * P[0][0] + C_1 * P[1][0] (Kalman) */
    const float                t_1_1 = C_0_1 * P[0][1]; /* + C_1 * P[1][1]  = 0 (Kalman) */


    P1[0][0] -= K_0_1 * t_0_1; //(Kalman)
    P1[0][1] -= K_0_1 * t_1_1; //(Kalman)
    P1[1][0] -= K_1_1 * t_0_1; //(Kalman)
    P1[1][1] -= K_1_1 * t_1_1; //(Kalman)
    angleX        += K_0_1 * angle_errX; //(Kalman)
    q_bias_X        += K_1_1 * angle_errX; //(Kalman)


    const float           angle_mY = atan2( ay_m, az_m );  // y axis
    const float           angle_errY = angle_mY - angleY;  // y axis
    const float           C_0_2 = 1;
    const float           PCt_0_2 = C_0_2 * P2[0][0];
    const float           PCt_1_2 = C_0_2 * P2[1][0];
    const float           E_2 =R_angle+ C_0_2 * PCt_0_2;
    const float           K_0_2 = PCt_0_2 / E_2;
    const float           K_1_2 = PCt_1_2 / E_2;
    const float           t_0_2 = PCt_0_2;
    const float           t_1_2 = C_0_2 * P2[0][1];

    P2[0][0] -= K_0_2 * t_0_2;
    P2[0][1] -= K_0_2 * t_1_2;
    P2[1][0] -= K_1_2 * t_0_2;
    P2[1][1] -= K_1_2 * t_1_2;
    angleY += K_0_2 * angle_errY;
    q_bias_Y += K_1_2 * angle_errY;

    const float           angle_mZ = averageZ/5;
    const float           angle_errZ = angle_mZ - angleZ;
    const float           C_0_3 = 1;
    const float           PCt_0_3 = C_0_3 * P3[0][0];
    const float           PCt_1_3 = C_0_3 * P3[1][0];
    const float           E_3 =R_angle+ C_0_3 * PCt_0_3;
    const float           K_0_3 = PCt_0_3 / E_3;
    const float           K_1_3 = PCt_1_3 / E_3;
    const float           t_0_3 = PCt_0_3;
    const float           t_1_3 = C_0_3 * P3[0][1];

    P3[0][0] -= K_0_3 * t_0_3;
    P3[0][1] -= K_0_3 * t_1_3;
    P3[1][0] -= K_1_3 * t_0_3;
    P3[1][1] -= K_1_3 * t_1_3;
    angleZ += K_0_3 * angle_errZ;
    q_bias_Z += K_1_3 * angle_errZ;

    rotationZ = angleZ;

    Serial.print("average z ");Serial.print(averageZ);Serial.print("  lastaverage z ");Serial.println(lastaverageZ);
    Serial.print("  angle_mZ = "); Serial.println(int(angle_mZ * 57.2957795130823));
    Serial.print("Rate  ");Serial.print(int(rate*57.2957795130823)); Serial.print("  Angle X  "); Serial.print(int(angleX*57.2957795130823)); Serial.print("  Angle Y  "); Serial.println(int(angleY*57.2957795130823)); //Prints degrees Acceleromter+Gyros+KalmanFilters
    Serial.print(joy_y_axis); //Prints the adjust for accelerometer jitter
    Serial.print(" ");
    Serial.print(int(angle*57.2957795130823)); //Prints degrees Acceleromter+Gyros+KalmanFilters
    Serial.print(" ");
    Serial.print(int(angle_m*57.295779513082)); //Prints the accelometer
    Serial.print(" ");
    Serial.println(joy_x_axis); //Prints the Gyro adjusment
  }
}
void print ()//This is the function to get the bytes from nintendo wii nunchuck
{
  joy_x_axis = outbuf[0];
  joy_y_axis = outbuf[1];
  ax_m = (outbuf[2] * 2 * 2) -511; //Axis X Acceleromter
  ay_m = (outbuf[3] * 2 * 2) -511; //Axis Y Acceleromter
  az_m = (outbuf[4] * 2 * 2) -511; //Axis Z Acceleromter

  // byte outbuf[5] contains bits for z and c buttons
  // it also contains the least significant bits for the accelerometer data
  // so we have to check each bit of byte outbuf[5]


  if ((outbuf[5] >> 2) & 1)
  {
    ax_m += 2;
  }
  if ((outbuf[5] >> 3) & 1)
  {
    ax_m += 1;
  }

  if ((outbuf[5] >> 4) & 1)
   {
   ay_m += 2;
   }
   if ((outbuf[5] >> 5) & 1)
   {
   ay_m += 1;
   }

  if ((outbuf[5] >> 6) & 1)
  {
    az_m += 2;
  }
  if ((outbuf[5] >> 7) & 1)
  {
    az_m += 1;
  }


}




沒有留言:

張貼留言