Advertisement
dustinrobotics

MotionPlus with NunChuck for Arduino 1.0

Apr 1st, 2012
538
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.35 KB | None | 0 0
  1. // .......................................................................
  2. // Code for reading data from a Wii Motion plus device connected to a Nunchuck
  3. // Links to other sites
  4. // http://www.windmeadow.com/node/42   .... first time i saw arduino nunchuk interface
  5. // http://www.kako.com/neta/2009-017/2009-017.html# .....Japanese site with lots of Wii info
  6. // http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Wii_Motion_Plus    .... the one and only
  7. // http://randomhacksofboredom.blogspot.com/2009/06/wii-motion-plus-arduino-love.html
  8. // ....original motion plus but not passthrough
  9. // http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248889032/35   .... great Kalman filter code
  10. // thanks to duckhead and knuckles904. I will be using that code for sure.
  11. // http://obex.parallax.com/objects/471/   .... ideas for passthrough
  12. // by Krulkip
  13. // Here is the bitmapping which is changed from the standard nunchuk mapping
  14. // In Nunchuk mode:
  15. //      Bit
  16. // Byte     7    6   5   4   3   2   1   0
  17. // 0     SX<7-----------------------------------------------------0>
  18. // 1     SY<7-----------------------------------------------------0>
  19. // 2     AX<9-----------------------------------------------------2>
  20. // 3     AY<9-----------------------------------------------------2>
  21. // 4     AZ<9---------------------------------------------3>    1
  22. // 5     AZ<2-----1>     AY<1>   AX<1>   BC BZ   0   0
  23. // Please note also the loss of the bit0 resolution.
  24. // Byte 5 Bit 0 and 1 is used for nunchuk (0 0) = nunchuk or motion plus  (1 0) is motion plus detection.
  25. // Byte 4 Bit 0 which is the extention controller detection bit. (1 = extension present)
  26. // Hardware Arduino with ATMega 168
  27. // Connections SCA to AD4 (Analog4) and SCL to AD5 (Analog5)
  28. /*      
  29.      Constants for scaling accelerometer values: Example of the accelerometer rotation values for y axis on Ardvark's nunchuk.
  30.    
  31.      Exact values differ between x and y axis, and probably between nunchuks, but the range value may be constant:
  32.    
  33.                   298 ayMIN
  34.                   _|_
  35.                 /     \
  36.    ayMID 513 - |       | - 513 ayMID
  37.                 \ _ _ /
  38.                    |
  39.                   728 ayMAX
  40.              
  41.      ayRange = (ayMID - ayMIN)*2 = ayMAX - ayMIN = 430
  42. */
  43. //=========================================================
  44. // changelog
  45. // ---------
  46. // 8/9/2009 - by dimkasta @ www.kastaniotis.net
  47. // Adapted for use with the transistor-less switching code originally written by krulkip on
  48. // http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248889032/60
  49. // Calibration and utility code based on the code by duckhead
  50. // 9/9/2009 - by dimkasta @ www.kastaniotis.net
  51. // Added Accelerometer calibration
  52. // Removed the casting to double and the multiplying with millis/sec from the cycle saving to further greately
  53. // increase the free time to ~1/50 in comparison to the reading time.
  54. // 18/9/2009 - by Ardvark
  55. // Slow/fast bit reading fixed: Slow degree/sec scale value appears correct.
  56. // Included details for absolute scaling of accelerometer values.
  57. // Included complementary filter for combining single accelerometer/gyro axes to produce drift- and translation-suppressed orientation values.
  58. //=========================================================
  59. // TODO LIST - Known issues
  60. // ---------
  61. // Assign common names to gyro and accel axes
  62. //........................................................................
  63.  
  64. #include <Wire.h>
  65.  
  66. unsigned long Now = millis();
  67. unsigned long lastread = Now;
  68. unsigned long lastreadMP = Now;
  69. //The system clock function millis() returns a value in milliseconds.  We need it in seconds.
  70. //Instead of dividing by 1000 we multiply by 0.001 for performance reasons.
  71.  
  72. boolean debug = true;
  73.  
  74. boolean calibrating = false;
  75.  
  76. uint8_t data[6];        // array to store arduino output
  77. int cnt = 0;
  78. int ledPin = 13;
  79. int xID;
  80.  
  81. //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
  82. static const int wmpSlowToDegreePerSec = 16; //when gyro speed 1, 16 units = 1°/s
  83. static const int wmpFastToDegreePerSec = 4; //when gyro speed 0, 4 units = 0,25°/s
  84.  
  85. // Hardware identifiers. Polling data[5]&0x03 for present device
  86. static const int NC = 0x00;
  87. static const int MP = 0x02;
  88. int currentDevice;
  89.  
  90. // WM+ state variables - if true, then in slow (high res) mode
  91. boolean slowYaw = 0;
  92. boolean slowPitch = 0;
  93. boolean slowRoll = 0;
  94.  
  95. int rollScale;
  96. int pitchScale;
  97. int yawScale;
  98.  
  99. // RK integration not currently in use
  100. struct RungeKutta {
  101.   double val_i_3;
  102.   double val_i_2;
  103.   double val_i_1;
  104.   double previous;
  105. };
  106.  
  107. struct RungeKutta rollRK;
  108. double roll_intRK;
  109.  
  110. // Calibrated gyro values (- yaw0 etc)
  111. int yaw = 0;
  112. int pitch = 0;
  113. int roll = 0;
  114. float rollf = 0;
  115.  
  116. float roll_int = 0;
  117. float roll_int_rad = 0;
  118.  
  119. float dt; // msec
  120. float dtMP; // this is the time elapsed between readings of the motion plus (MP)
  121.  
  122. float rollCF = 0;
  123. float pitchCF = 0;
  124.  
  125. static const double DegreeToRadian = 0.0174532925;  // PI/180
  126.  
  127. // Complimentary filter: inspired by Shane Colton's description in filter.pdf at http://web.mit.edu/first/segway/segspecs.zip
  128.  
  129. // Tweak tau to alter the performance of the complimentary filter.
  130. // Increase tau: influence of the gyro input increases (less translation artifacts).
  131. // Reduce tau: influence of the accelerometer input increases (faster drift-correction).
  132. // This filtering is temporal: translations that occur over a time scale shorter than tau will be suppressed,
  133. // and gyro changes that occur over a time scale longer than tau will have their drift corrected.
  134. // NB: changes in the read rate of the gyro (dtMP) will change the filter's performance (change tau to compensate).
  135.  
  136. static const float tau = 0.95; // unit: seconds.
  137. float compFilter(float prevValue, int gyro, double accel, float timeStep) {
  138.   float a = tau/(tau + dtMP);
  139.   float b = 1 - a;
  140.   return (float) (a*(prevValue + ((gyro*DegreeToRadian)*timeStep)) + (b*accel));
  141. }
  142.  
  143. // accelerometer values
  144. int ax_m = 0;
  145. int ay_m = 0;
  146. int az_m = 0;
  147.  
  148. // Exact values differ between x and y axis, and probably between nunchuks, but the range value may be constant:
  149. static const int axMIN = 290;
  150. static const int axMAX = 720;
  151. static const int axMID = (axMAX - axMIN)/2 + axMIN;
  152. static const int axRange = (axMID - axMIN)*2;
  153.  
  154. static const int ayMIN = 298;
  155. static const int ayMAX = 728;
  156. static const int ayMID = (ayMAX - ayMIN)/2 + ayMIN;
  157. static const int ayRange = (ayMID - ayMIN)*2;
  158.  
  159. // Not sure what a meaningful scale is for the z accelerometer axis so:
  160.  
  161. static const int azMID = ayMID;
  162. static const int azRange = ayRange;
  163.  
  164. // raw 3 axis gyro readings MP+ //gyro readings => yaw/yawscale
  165. int readingsX;
  166. int readingsY;
  167. int readingsZ;
  168.  
  169. // Nunchuck variables //NunChuck X angle NOT readings
  170. float accelAngleX=0;    //NunChuck X angle
  171. float accelAngleY=0;    //NunChuck Y angle
  172. float accelAngleZ=0;    //NunChuck Z angle
  173. float theta=0;  
  174.  
  175. int z_button = 0;
  176. int c_button = 0;
  177. int joy_x_axis = 0;
  178. int joy_y_axis = 0;
  179.  
  180. // calibration zeroes
  181. //gyros
  182. int yaw0 = 0;
  183. int pitch0 = 0;
  184. int roll0 = 0;
  185.  
  186. //accelerometers
  187. int yawAcc0 = 0;
  188. int pitchAcc0 = 0;
  189. int rollAcc0 = 0;
  190.  
  191. double x;
  192. double y;
  193. double z;
  194.  
  195.  
  196. //Nunchuk accelerometer value to radian conversion.
  197. float angleInRadians(int range, int measured) {
  198.   float x = range;
  199.   return (float)(measured/x) * PI;
  200. }
  201.  
  202. void
  203. setup ()
  204. {
  205.   Serial.begin (115200);
  206.   Wire.begin();
  207.  
  208.   initializeSensors();
  209.   calibrateZeroes();
  210.  
  211.   rollRK.val_i_1 = 0;
  212.   rollRK.val_i_2 = 0;
  213.   rollRK.val_i_3 = 0;
  214.   rollRK.previous = 0;
  215.  
  216. }
  217.  
  218. void send_zero ()
  219. {
  220.     Wire.beginTransmission(0x52);
  221.     Wire.write((uint8_t)0x00);
  222.     Wire.endTransmission();
  223. }
  224.  
  225. void loop ()
  226. {
  227.   Now = millis();
  228.   dt = Now - lastread; //compute the time delta since last iteration, in msec.
  229.   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
  230.   {
  231.      readData();
  232.      lastread = Now;
  233.   }
  234.   else // just for demo of the free cycles
  235.   {
  236.   //delay(10);
  237.   //Serial.println("Free cycle");
  238.   }
  239.   delay(100);
  240. }
  241.  
  242. void readData()
  243. {  
  244.   long startReading = millis();
  245.   //digitalWrite(13,HIGH);  // first flash the LED to show activity
  246.  
  247.     send_zero (); // send the request for next bytes
  248.     //delay (10);
  249.    
  250.     // now follows conversion command instructing extension controller to get
  251.     // all sensor data and put them into 6 byte register within the extension controller
  252.     Wire.requestFrom (0x52,6);
  253.     data[0] = Wire.read();
  254.     data[1] = Wire.read();
  255.     data[2] = Wire.read();
  256.     data[3] = Wire.read();
  257.     data[4] = Wire.read();
  258.     data[5] = Wire.read();
  259.  
  260.  
  261.     parseData();
  262.   if(false)
  263.   {
  264.     long endReading = millis() - startReading;
  265.     Serial.print("Finished Reading in ");
  266.     Serial.print(endReading);
  267.     Serial.println(" milliseconds");
  268.   }
  269.   // digitalWrite(13,LOW);  // first flash the LED to show activity
  270.  
  271. }
  272.  
  273. void parseData ()
  274. {
  275.   // check if nunchuk is really connected
  276.   if ((data[4]&0x01)==0x01) {
  277.   //printLine("Ext con: ");
  278.  
  279.   currentDevice = data[5]&0x03;
  280.   }
  281.     if (currentDevice == NC)
  282.     {
  283.  
  284.   // Dimitris version of accl readings
  285.       ax_m = (data[2] << 2) + ((data[5] >> 3) & 2) ;
  286.       ay_m = (data[3] << 2) + ((data[5] >> 4) & 2);
  287.       az_m = (data[4] << 2) + ((data[5] >> 5) & 6) ;
  288.  
  289.         //Zero the values on a#MID.
  290.         ax_m -= axMID;
  291.         ay_m -= ayMID;
  292.         az_m -= azMID;
  293.      
  294.  
  295.      
  296.  // stitch
  297.  
  298.  // Convert to radians by mapping 180 deg of rotation to PI
  299.         x = angleInRadians(axRange, ax_m);
  300.         y = angleInRadians(ayRange, ay_m);
  301.         z = angleInRadians(azRange, az_m);
  302.        
  303.         //compute values that are used in multiple places
  304. //        double xSquared = x*x;
  305. //        double ySquared = y*y;
  306. //        double zSquared = z*z;
  307. //      
  308. //        accelAngleX = atan(x / sqrt(ySquared + zSquared));
  309. //        accelAngleY = atan(y / sqrt(xSquared + zSquared));
  310. //        theta       = atan(sqrt(xSquared + ySquared) / z);
  311.  
  312.    }
  313.   else
  314.   if  (currentDevice == MP)
  315.   {
  316.     dtMP = (Now - lastreadMP)/1000.0; //compute the time delta since last MP read, in sec.
  317.     lastreadMP = Now;
  318.    
  319.       // duck's hardware setup for this code has the MP spun 90 deg relative to the NC, so I swapped the roll and pitch to match a setup with the NC and MP on the same axis
  320.       // Gyroscopes: dual-axis IDG-600, see http://www.invensense.com/products/idg_600.html and EPSON TOYOCOM labelled X3500W (yaw)
  321.     roll = (((data[4]&0xFC)<<6) + data[1]);
  322.     pitch = (((data[5]&0xFC)<<6) + data[0]);
  323.     yaw = (((data[3]&0xFC)<<6) + data[2]);
  324.    
  325.     if(calibrating == false) //Dont compensate if we are doing the calibration measurements.
  326.     {
  327.      
  328.       slowPitch = (data[4]&0x02)>>1;
  329.       slowRoll = (data[3]&0x01)>>0;
  330.       slowYaw = (data[3]&0x02)>>1;
  331.      
  332.       rollScale  = slowRoll  ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec; // if speed == 1, then wmpSlowToDegreePerSec, else wmpFastToDegreePerSec
  333.       pitchScale = slowPitch ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec;
  334.       yawScale   = slowYaw   ? wmpSlowToDegreePerSec : wmpFastToDegreePerSec;
  335.      
  336.       //Calibrating
  337.       roll -= roll0;
  338.       pitch -= pitch0;
  339.       yaw -= yaw0;
  340.      
  341.       roll = roll/rollScale;    // deg/s = mV/(mV/deg/s)
  342.       pitch = pitch/pitchScale;
  343.       yaw = yaw/yawScale;
  344.      
  345.       // Complimentary filter: inspired by Shane Colton's description in filter.pdf at http://web.mit.edu/first/segway/segspecs.zip
  346.         rollCF = compFilter(rollCF, roll, x, dtMP);
  347.         pitchCF = compFilter(pitchCF, pitch, y, dtMP);
  348.  
  349.     }
  350.  
  351.   }
  352.  
  353.  
  354.   if(debug)
  355.   {
  356.     //Uncomment the set that you want to monitor.
  357.     //Having everything spammed is not very helpful
  358.    
  359.    // Serial.print ("joyx= ");
  360.    // Serial.println (joy_x_axis);
  361.  
  362.    // Serial.print ("joyy= ");
  363.    // Serial.println (joy_y_axis);
  364. //  
  365. //     Serial.print (" ");
  366. //     Serial.print(slowRoll==1);
  367. //  
  368. //    Serial.print (" ");
  369. //    Serial.print(slowPitch==1);
  370. //    
  371. //    Serial.print (" ");
  372. //    Serial.print(slowYaw==1);
  373. //    
  374. //     Serial.print (" ");
  375. //     Serial.print (rollScale);
  376. //    
  377. //    Serial.print (" ");
  378. //    Serial.print (x);
  379.      
  380. //    Serial.print (" ");
  381. //    Serial.print (y);
  382.  
  383. //    Serial.print (" ");
  384. //    Serial.print (z);
  385.    
  386. //    Serial.print (" ");
  387. //    Serial.println (rollCF);
  388.    
  389. //    Serial.print (" ");
  390. //    Serial.println (pitchCF);
  391. //    
  392. //    Serial.print (" ");
  393. //    Serial.println (a);
  394. //    
  395. //    Serial.print (" ");
  396. //    Serial.print (accelAngleX);
  397. //      
  398. //    Serial.print (" ");
  399. //    Serial.print (accelAngleY);
  400. //      
  401. //    Serial.print (" ");
  402. //    Serial.println (theta);
  403. //
  404. //    Serial.print (" ");
  405. //    Serial.print (ax_m);
  406. //      
  407. //    Serial.print (" ");
  408. //    Serial.print (ay_m);
  409. ////      
  410. //    Serial.print (" ");
  411. //    Serial.print (az_m);
  412.      
  413. //    Serial.print (" ");
  414. //    Serial.print (ax_m_rad);
  415. //      
  416. //    Serial.print (" ");
  417. //    Serial.print (ay_m_rad);
  418. //      
  419. //    Serial.print (" ");
  420. //    Serial.println (az_m_rad);
  421.    
  422.    // if (z_button == 0) { Serial.println ("z_button "); }
  423.    // if (c_button == 0) { Serial.println("c_button "); }
  424. //  
  425.      Serial.print (" ");
  426.      Serial.print (roll);
  427. //  
  428.      Serial.print (" ");
  429.      Serial.print (pitch);
  430. //  
  431.      Serial.print (" ");
  432.      Serial.println (yaw);
  433. //    
  434. //     Serial.print (" ");
  435. //     Serial.print(slowPitch);
  436. //     Serial.print (" ");
  437. //     Serial.println(slowYaw);
  438. //     Serial.print (" ");
  439. //     Serial.println(dt);
  440.   }
  441.   // Finished with data assignment, now we need calibration
  442.  
  443. }
  444.  
  445. void initializeSensors()
  446. {
  447.  
  448.   digitalWrite(13,HIGH);  // first flash the LED to show activity
  449.   Serial.println ("Now detecting WM+");
  450.   //delay(100);
  451.   // now make Wii Motion plus the active extension
  452.   // nunchuk mode = 05, wm+ only = 04, classic controller = 07
  453.   Serial.println ("Activating WM+ in NC mode (5)...");
  454.    Wire.beginTransmission(0x53);
  455.    Wire.write((uint8_t)0xFE);
  456.    Wire.write((uint8_t)0x05);// nunchuk
  457.    Wire.endTransmission();
  458.   Serial.println (" OK done");
  459.    //delay (100);
  460.   // now innitiate Wii Motion plus
  461.    Serial.println ("Innitialising Wii Motion plus ........");
  462.     Wire.beginTransmission(0x53);
  463.     Wire.write((uint8_t)0xF0);
  464.     Wire.write((uint8_t)0x55);
  465.     Wire.endTransmission();
  466.   Serial.println(" OK done");
  467.     //delay (100);
  468.   Serial.println ("Reading address at 0xFA .......");
  469.     Wire.beginTransmission(0x52);
  470.     Wire.write((uint8_t)0xFA);
  471.     Wire.endTransmission();
  472.   Serial.println(" OK done");
  473.    //delay (100);
  474.    Wire.requestFrom (0x52,6);
  475.    data[0] = Wire.read();//Serial.print(outbuf[0],HEX);Serial.print(" ");
  476.    data[1] = Wire.read();//Serial.print(outbuf[1],HEX);Serial.print(" ");
  477.    data[2] = Wire.read();//Serial.print(outbuf[2],HEX);Serial.print(" ");
  478.    data[3] = Wire.read();//Serial.print(outbuf[3],HEX);Serial.print(" ");
  479.    data[4] = Wire.read();//Serial.print(outbuf[4],HEX);Serial.print(" ");
  480.    data[5] = Wire.read();//Serial.print(outbuf[5],HEX);Serial.print(" ");
  481.  
  482.    xID= data[0] + data[1] + data[2] + data[3] + data[4] + data[5];
  483.   Serial.println("Extension controller xID = 0x");
  484.    Serial.print(xID,HEX);
  485.    if (xID == 0xCB) { Serial.println ("MP+ on but not active"); }
  486.    if (xID == 0xCE) { Serial.println ("MP+ on and active"); }
  487.    if (xID == 0x00) { Serial.println ("MP+ not on"); }
  488.    //delay (100);
  489.    // Now we want to point the read adress to 0xa40008 where the 6 byte data is stored
  490.   Serial.println ("Reading address at 0x08 .........");
  491.     Wire.beginTransmission(0x52);
  492.     Wire.write((uint8_t)0x08);
  493.     Wire.endTransmission();
  494.   Serial.println(" OK done");
  495.   Serial.println ("Finished setup");
  496.  
  497.   digitalWrite(13,LOW); // Led off
  498. }
  499.  
  500. /*
  501.  * Find the steady-state of the WM+ gyros.  This function reads the gyro values 100 times and averages them.
  502.  * Those values are used as the baseline steady-state for all subsequent reads.  As a result it is very
  503.  * important that the device remain relatively still during startup.
  504.  */
  505.  
  506.  
  507. void calibrateZeroes(){  // wiibrew: While the Wiimote is still, the values will be about 0x1F7F (8,063)
  508.   calibrating = true;
  509.   if(debug)
  510.   {
  511.     Serial.println("Starting calibration...");
  512.   }
  513.   //Gyros
  514.   long y0 = 0;
  515.   long p0 = 0;
  516.   long r0 = 0;
  517.  
  518.   //Accelerometers for application calibration, not for zeroing the starting points.
  519.   long ya0 = 0;
  520.   long pa0 = 0;
  521.   long ra0 = 0;
  522.  
  523.   const int avg = 200; //100 reads of the gyros and 100 of the accelerometers
  524.  
  525.   digitalWrite(13,HIGH);  // first flash the LED to show activity
  526.  
  527.   for (int i=0;i<avg; i++)
  528.   {
  529.     delay(10); // bypasses the normal global delays
  530.     readData();
  531.    
  532.     if(currentDevice == MP)
  533.     {
  534.       if(debug)
  535.       {
  536.         Serial.println("<= Data from MP");
  537.       }
  538.    
  539.       y0+=yaw;
  540.       r0+=roll;
  541.       p0+=pitch;
  542.     }
  543.     else
  544.     {
  545.        if(debug)
  546.       {
  547.         Serial.println("<= Data from NC...");
  548.       }
  549.       ya0 += az_m;
  550.       pa0 += ay_m;
  551.       ra0 += ax_m;
  552.      
  553.     }
  554.   }
  555.  
  556.   yaw0 = y0/(avg/2);
  557.   roll0 = r0/(avg/2);
  558.   pitch0 = p0/(avg/2);
  559.  
  560.   yawAcc0 = ya0/(avg/2);
  561.   rollAcc0 = ra0/(avg/2);
  562.   pitchAcc0 = pa0/(avg/2);
  563.  
  564.   calibrating = false;
  565.  
  566.   digitalWrite(13,LOW); // Led off
  567.   if(debug)
  568.   {
  569.     Serial.println("Calibration results...");
  570.     Serial.print("Yaw0:");
  571.     Serial.println(yaw0);
  572.     Serial.print("Roll0:");
  573.     Serial.println(roll0);
  574.     Serial.print("Pitch0:");
  575.     Serial.println(pitch0);
  576.     Serial.print("YawAcc0:");
  577.     Serial.println(yawAcc0);
  578.     Serial.print("RollAcc0:");
  579.     Serial.println(rollAcc0);
  580.     Serial.print("PitchAcc0:");
  581.     Serial.println(pitchAcc0);
  582.   }
  583. }
  584.  
  585. /*
  586.  * Compute the common fourth-order Runge-Kutta algorithm as part of the integrating the gyro's yaw signal.  This
  587.  * will smooth the values a tad.  Gyro drift is still a problem, however.
  588.  *
  589.  * rk       the RungaKutta struct to hold previous values
  590.  * val_i_0  the latest raw value to integrate
  591.  *
  592.  * returns the RK4 approximation of all values up until time t
  593.  */
  594. double computeRK4(struct RungeKutta *rk, double val_i_0) {
  595.   rk->previous += 0.16667*(rk->val_i_3 + 2*rk->val_i_2 + 2*rk->val_i_1 + val_i_0);
  596.   rk->val_i_3 = rk->val_i_2;
  597.   rk->val_i_2 = rk->val_i_1;
  598.   rk->val_i_1 = val_i_0;
  599.   return rk->previous;
  600. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement