Monday, 26 September 2016

Measuring rotation with Arduino and GY-521 (MPU-6050 Accelerometer + Gyro)

Blog posts have been a bit thin on the ground this month. Mostly that's due to a house move (Nerd Towers has once again relocated) as well as constructing a new workshop to house all our electronics goodies as well as recovering the laser cutter and CNC and other "cool tools" to get them all back into one place.

But we've also been pretty busy with real-life work.
Not just hacking code together, but actually putting some of our (not inconsiderable) experience to use, making cool stuff. Sadly, a lot of it is commercially sensitive. Hence the lack of posts, even when a few of us have been working on some really nerdy projects!

Anyway, here's a quick post about a generic idea we've been playing about with. It's basically a way of allowing us to use real-world objects as controllers. It was mentioned a little while back and we were using an accelerometer to measure the rotation of an object around the horizontal axis (holding an object horizontally and rotating it around the y-axis)



This time, we wanted to measure rotation around the vertical (or Z) axis.
This proved a little more difficult. Try as we might, no amount of messing about with accelerometers was going to get us a workable result. Maybe it's because we'd be moving the object at exactly 90 degrees to the direction in which the acclerometer worked (i.e. it can measure any amount of rotation in relation to gravity - the vertical plane - but can't work out rotation in relation to a plane at 90 degrees - i.e. the horizontal plane). In fact, we're pretty convinced that this is exactly why we couldn't get a workable solution.

So then we took to Inkscape and drew a PCB which allowed us to place 12 hall sensors in a ring. By placing a small magnet inside our object, we could place it inside the ring of hall sensors and rotate it. It didn't matter which way up the object/PCB was - as long as the object was inside the hall sensors, we could consistently detect it's rotation. Bingo! A working solution.

Except now we're looking to remove the need for the object to sit inside a ring of sensors, and for it to work on pretty much any surface. We tried a few different ideas but the one that worked consistently well was the GY-521 module.

Sure, it's an MPU-6050 Accelerometer - and we know accelerometers are no good for detecting rotation in the horizontal plane. But it also has an onboard gyro. Which can.

Now unlike accelerometers, which give you a position in space, based on the effect of gravity on each of three different axes, a gyro only reports angular velocity - i.e. when the direction of movement is changing in any axis. Which means it only gives meaningful values while its rotating and doesn't report it's position in space, when it's standing still.

To demonstrate this, we used the example code from the Arduino Playground page and mapped the output readings to a graph in Google Spreadsheets. We ran the code, rotated the sensor around the z-axis and drew a graph of results.



When we did this in the past - with the accelerometer for example - we at least got some recognisable kind of patterns (sine waves) with some extraneous noise. This time our values bear no relation to each other - it's just a jumble of noise! The spikes and troughs in the middle reading relate not to the position of the sensor in the vertical plane, but the velocity of the sensor as it rotated around the vertical axis.

With all this in mind, we put together some simple firmware for registering rotation "events":

#include<Wire.h>
const int MPU_addr=0x68;nbsp;  // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;

int curr_Y = 0;
int last_Y = 0;
int curr_val = 0;
int max_val = 12;
int min_speed = 50;


void sendMsg(int d){
   // this just simply reports whether we've started moving in a clockwise
   // or anti-clockwise direction and updates a counter/pointer accordingly
   
   if(d < 0){
       Serial.println("anti-clockwise");
       curr_val--;
       if(curr_val < 0){ curr_val = max_val;}  
   }else{
       Serial.println("clockwise");
       curr_val++;
       if(curr_val > max_val){ curr_val = 0;}  
   }
   Serial.print("value: ");
   Serial.println(curr_val);
}

void setup(){
   Wire.begin();
   Wire.beginTransmission(MPU_addr);
   Wire.write(0x6B);   // PWR_MGMT_1 register
   Wire.write(0);        // set to zero (wakes up the MPU-6050)
   Wire.endTransmission(true);
   Serial.begin(9600);
}

void loop(){
   Wire.beginTransmission(MPU_addr);
   Wire.write(0x3B);   // starting with register 0x3B (ACCEL_XOUT_H)
   Wire.endTransmission(false);
   Wire.requestFrom(MPU_addr,14,true);    // request a total of 14 registers
   AcX=Wire.read()<<8|Wire.read();    // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
   AcY=Wire.read()<<8|Wire.read();    // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
   AcZ=Wire.read()<<8|Wire.read();    // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
   Tmp=Wire.read()<<8|Wire.read();    // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
   GyX=Wire.read()<<8|Wire.read();    // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
   GyY=Wire.read()<<8|Wire.read();    // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
   GyZ=Wire.read()<<8|Wire.read();    // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
   
   // measure rotation around the Y-axis to work out
   // active rotation (the giro only measures AS THE
   // thing rotates, it doesn't report absolute position)
   
   // reduce the sensitivity
   GyX = (GyX >> 8) & 0xFF;
   GyY = (GyY >> 8);
   GyZ = (GyZ >> 8) & 0xFF;
   
   //Serial.print(" | GyX = "); Serial.print(GyX);
   
   curr_Y = 0;
   if(GyY < (0-min_speed) || GyY > min_speed ){
       curr_Y = GyY;

       if(curr_Y < 0 && last_Y < 0){
           // this is a continuation of a rotation movement
       }else if(curr_Y > 0 && last_Y > 0){
           // this is a continuation of a rotation movement
       }else if((curr_Y >= 0 && last_Y < 0) || (curr_Y <= 0 && last_Y > 0) || last_Y==0 ){
           // this is a change in rotation, so trigger "rotated"
           sendMsg(curr_Y);
           // now we don't want this to repeat to quickly so
           // let's just shove a (blocking) delay in here
           delay(250);nbsp;
       }
   }

   last_Y = curr_Y;
}

And here's a video showing it in action....