arduino + mpu 5060

This article will teach how to control a simple interface made in Processing through the movement of an MPU6050 sensor using the Wekinator software platform. 

We'll start the project by interfacing the MPU6050 sensor with the Arduino UNO and sending the sensor output data to Processing. 

In Processing, we'll calculate the YPR (yaw, pitch, roll) values and draw a 3D model that reflects the sensor's motions. 

Next, we send the YPR values to the Wekinator. 

After training, it will send the output data to the interface, which is controlled using the Dynamic Time Warping (DTW) algorithm. 

To learn more about using Dynamic Time Warping in projects, check out our article, How to Program Dynamic Time Warping with Machine Learning.

Input Instructions

For the input aspect of this project, we'll need to connect the MPU6050 sensor with the Arduino UNO. 

Reference the figure below for help on how to connect the sensor to the Arduino. 

interface_wekinator7.png

Figure detailing the connections between the MPU6050 sensor and Arduino UNO.

Installing the Arduino Libraries 

First, download the I2C and MPU6050 library from GitHub in order to interface with the Arduino. 

After unzipping or extracting the download file, navigate to the Arduino folder, copy the I2C and MPU6050 folders and place them in the Arduino IDE library folder. 

interface_wekinator5.png

The location of the I2C and MPU6050 folders in the Arduino IDE library folder. 

 

 

Uploading Code Process

 

 

 

Sub-title here

  • Launch the Arduino IDE. 
  • Find the examples file under the MPU6050 folder. 
  • Open the MPU6050_DMP6 file. 

interface_wekinator9.png

Location of the MPU6050_DMP6 file under examples and the MPU6050 folder. 

Now, upload the Arduino IDE code and the serial monitor will come up. 

If it displays the output then that's an indication you've successfully interfaced the sensor with the Arduino. 

interface_wekinator11.png

The output data displaying in the Arduino. 

To send the data to Processing, some changes need to be made to the code. 

First uncomment the 117 code line and comment the 100 code line. 

interface_wekinator2.png

Upload the code again and it should show as unreadable characters in the serial monitor. 

interface_wekinator8.png

 

 

 

 

Processing Code Instructions

In order to receive data from the Arduino and move the 3D model, the 'toxiclibs' library will need to be downloaded from bitbucket.org. 

interface_wekinator1.png

Copy all folders within the zip file and paste them into the Processing library folder. 

The Processing library folder can be found under: Processing Folder > Modes > Java > Libraries. 

Now, paste the code below into Processing then upload. 

The code is a modified version of the example included in the MPU6050 library. 


 

javascriptCopy

import processing.serial.*; import processing.opengl.*; import toxi.geom.*; import toxi.processing.*; import oscP5.*; import netP5.*; OscP5 oscP5; NetAddress dest; ToxiclibsSupport gfx; Serial port; // The serial port char[] teapotPacket = new char[14]; // InvenSense Teapot packet int serialCount = 0; // current packet byte position int synced = 0; int interval = 0; float[] q = new float[4]; Quaternion quat = new Quaternion(1, 0, 0, 0); float[] gravity = new float[3]; float[] euler = new float[3]; float[] ypr = new float[3]; void setup() { // 300px square viewport using OpenGL rendering size(300, 300, OPENGL); gfx = new ToxiclibsSupport(this); // setup lights and antialiasing lights(); smooth(); // display serial port list for debugging/clarity println(Serial.list()); // get the first available port (use EITHER this OR the specific port code below) String portName = Serial.list()[0]; // get a specific serial port (use EITHER this OR the first-available code above) //String portName = "COM4"; // open the serial port port = new Serial(this, portName, 115200); // send single character to trigger DMP init/start // (expected by MPU6050_DMP6 example Arduino sketch) port.write('r'); /* start oscP5, sending messages at port 9000 */ oscP5 = new OscP5(this,9000); dest = new NetAddress("127.0.0.1",6448); } void draw() { if (millis() - interval > 1000) { // resend single character to trigger DMP init/start // in case the MPU is halted/reset while applet is running port.write('r'); interval = millis(); } // black background background(0); // translate everything to the middle of the viewport pushMatrix(); translate(width / 2, height / 2); // 3-step rotation from yaw/pitch/roll angles (gimbal lock!) // ...and other weirdness I haven't figured out yet //rotateY(-ypr[0]); //rotateZ(-ypr[1]); //rotateX(-ypr[2]); // toxiclibs direct angle/axis rotation from quaternion (NO gimbal lock!) // (axis order [1, 3, 2] and inversion [-1, +1, +1] is a consequence of // different coordinate system orientation assumptions between Processing // and InvenSense DMP) float[] axis = quat.toAxisAngle(); rotate(axis[0], -axis[1], axis[3], axis[2]); // draw main body in red fill(255, 0, 0, 200); box(10, 10, 200); // draw front-facing tip in blue fill(0, 0, 255, 200); pushMatrix(); translate(0, 0, -120); rotateX(PI/2); drawCylinder(0, 20, 20, 8); popMatrix(); // draw wings and tail fin in green fill(0, 255, 0, 200); beginShape(TRIANGLES); vertex(-100, 2, 30); vertex(0, 2, -80); vertex(100, 2, 30); // wing top layer vertex(-100, -2, 30); vertex(0, -2, -80); vertex(100, -2, 30); // wing bottom layer vertex(-2, 0, 98); vertex(-2, -30, 98); vertex(-2, 0, 70); // tail left layer vertex( 2, 0, 98); vertex( 2, -30, 98); vertex( 2, 0, 70); // tail right layer endShape(); beginShape(QUADS); vertex(-100, 2, 30); vertex(-100, -2, 30); vertex( 0, -2, -80); vertex( 0, 2, -80); vertex( 100, 2, 30); vertex( 100, -2, 30); vertex( 0, -2, -80); vertex( 0, 2, -80); vertex(-100, 2, 30); vertex(-100, -2, 30); vertex(100, -2, 30); vertex(100, 2, 30); vertex(-2, 0, 98); vertex(2, 0, 98); vertex(2, -30, 98); vertex(-2, -30, 98); vertex(-2, 0, 98); vertex(2, 0, 98); vertex(2, 0, 70); vertex(-2, 0, 70); vertex(-2, -30, 98); vertex(2, -30, 98); vertex(2, 0, 70); vertex(-2, 0, 70); endShape(); popMatrix(); //Send the OSC message sendOsc(); } void serialEvent(Serial port) { interval = millis(); while (port.available() > 0) { int ch = port.read(); if (synced == 0 && ch != '$') return; // initial synchronization - also used to resync/realign if needed synced = 1; print ((char)ch); if ((serialCount == 1 && ch != 2) || (serialCount == 12 && ch != '\r') || (serialCount == 13 && ch != '\n')) { serialCount = 0; synced = 0; return; } if (serialCount > 0 || ch == '$') { teapotPacket[serialCount++] = (char)ch; if (serialCount == 14) { serialCount = 0; // restart packet byte position // get quaternion from data packet q[0] = ((teapotPacket[2] << 8) | teapotPacket[3]) / 16384.0f; q[1] = ((teapotPacket[4] << 8) | teapotPacket[5]) / 16384.0f; q[2] = ((teapotPacket[6] << 8) | teapotPacket[7]) / 16384.0f; q[3] = ((teapotPacket[8] << 8) | teapotPacket[9]) / 16384.0f; for (int i = 0; i < 4; i++) if (q[i] >= 2) q[i] = -4 + q[i]; // set our toxilibs quaternion to new data quat.set(q[0], q[1], q[2], q[3]); // below calculations unnecessary for orientation only using toxilibs // calculate gravity vector gravity[0] = 2 * (q[1]*q[3] - q[0]*q[2]); gravity[1] = 2 * (q[0]*q[1] + q[2]*q[3]); gravity[2] = q[0]*q[0] - q[1]*q[1] - q[2]*q[2] + q[3]*q[3]; // calculate Euler angles euler[0] = atan2(2*q[1]*q[2] - 2*q[0]*q[3], 2*q[0]*q[0] + 2*q[1]*q[1] - 1); euler[1] = -asin(2*q[1]*q[3] + 2*q[0]*q[2]); euler[2] = atan2(2*q[2]*q[3] - 2*q[0]*q[1], 2*q[0]*q[0] + 2*q[3]*q[3] - 1); // calculate yaw/pitch/roll angles ypr[0] = atan2(2*q[1]*q[2] - 2*q[0]*q[3], 2*q[0]*q[0] + 2*q[1]*q[1] - 1); ypr[1] = atan(gravity[0] / sqrt(gravity[1]*gravity[1] + gravity[2]*gravity[2])); ypr[2] = atan(gravity[1] / sqrt(gravity[0]*gravity[0] + gravity[2]*gravity[2])); // output various components for debugging //println("q:\t" + round(q[0]*100.0f)/100.0f + "\t" + round(q[1]*100.0f)/100.0f + "\t" + round(q[2]*100.0f)/100.0f + "\t" + round(q[3]*100.0f)/100.0f); //println("euler:\t" + euler[0]*180.0f/PI + "\t" + euler[1]*180.0f/PI + "\t" + euler[2]*180.0f/PI); println("ypr:\t" + ypr[0]*180.0f/PI + "\t" + ypr[1]*180.0f/PI + "\t" + ypr[2]*180.0f/PI); } } } } void drawCylinder(float topRadius, float bottomRadius, float tall, int sides) { float angle = 0; float angleIncrement = TWO_PI / sides; beginShape(QUAD_STRIP); for (int i = 0; i < sides + 1; ++i) { vertex(topRadius*cos(angle), 0, topRadius*sin(angle)); vertex(bottomRadius*cos(angle), tall, bottomRadius*sin(angle)); angle += angleIncrement; } endShape(); // If it is not a cone, draw the circular top cap if (topRadius != 0) { angle = 0; beginShape(TRIANGLE_FAN); // Center point vertex(0, 0, 0); for (int i = 0; i < sides + 1; i++) { vertex(topRadius * cos(angle), 0, topRadius * sin(angle)); angle += angleIncrement; } endShape(); } // If it is not a cone, draw the circular bottom cap if (bottomRadius != 0) { angle = 0; beginShape(TRIANGLE_FAN); // Center point vertex(0, tall, 0); for (int i = 0; i < sides + 1; i++) { vertex(bottomRadius * cos(angle), tall, bottomRadius * sin(angle)); angle += angleIncrement; } endShape(); } } void sendOsc() { OscMessage msg = new OscMessage("/wek/inputs"); msg.add((float)ypr[2]); // x-axis msg.add((float)ypr[1]); // y -axis oscP5.send(msg, dest); } More

After uploading the code, a window should come up like the one below. 

Output Code Instructions 

In terms of the output process, a simple interface will be set up to receive one DTW output from Wekinator. 

Within the interface, a square box will move either left or right according to the Wekinator input it receives. 

You can find and download the Processing sketch on the Wekinator website. 

After downloading the 'Simple DTW-controlled-game' file and running it in Processing, it should look like the example below. 

interface_wekinator4.png

 

 

 

 

 

Wekinator Instructions 

Launch the Wekinator software and follow these steps: 

  • Set the input values to 2. 
  • Set the output values to 1. 
  • Leave the output type to the default setting, 'all dynamic time warping' and designate 3 gesture types. 

interface_wekinator14.png

'Create new project' window displaying the input, output and gesture type fields in Wekinator. 

Click 'Next' and the 'New Project' window will pop up. 

interface_wekinator10.png

'New Project' window with output row fields in Wekinator. 

Then, click the 'Plus' button across the output 1 row and tilt the sensor left. The output will move the box in that direction. 

interface_wekinator3.png

'New Project' window with Add/Remove buttons circled.

Now, click the 'Plus' button across the output 2 row and tilt the sensor right. The output will move the box accordingly. 

interface_wekinator12.png

'New Project' window with Add/Remove buttons in output 2 row circled in Wekinator.

Lastly, click the plus button in the output 3 row and tilt the sensor backwards. The output will cause the box to jump. 

interface_wekinator6.png

'New Project' window with Add/Remove buttons circled in Wekinator.

interface_wekinator6.png

'New Project' window with Add/Remove buttons in output 3 row circled. 

After recording, train the Wekinator according to the samples and run the program. 

The square box will then move in response to the direction the sensor is tilted.  

Les commentaires sont clôturés