Arduino Project – Biofeedback Device – Learning Meditation – Part 2 – Gyroscope Connection

If you are here for the first time, I encourage you to read the previous part – which is a kind of introduction – a description of the project and its genesis.

Since I have to wait for my Arduino nano, I decided to start with Arduino Uno, which “I have in stock”. Besides, it’s bigger and easier to prototype on it and connect all those magic cables 🙂

The beginning – in search of “how to do it” 🙂

Documentation for the GY-521 is available here.

Because I have not had contact with electronic documentation for a long time, I decided to look for more “human-understandable” descriptions. Fortunately, the Internet came to the rescue and I quickly found how to connect everything together. Google heavily in use :). Interestingly, in addition to the scheme, I also found programs to operate the module – but more on that in a moment.

We connect the cables

This is the connection diagram of the GY-521 module with Arduino:

Diagram of connecting the GY-521 gyroscope (MPU6050) to Arduino Uno

And this is how it looks in my set-up – using the prototype board:

It’s time for a USB data collection program

Here I do not darken. I had no idea how to start programming Arduino (even though I can program as such). That’s why I used the ready-made, and then analyzed it with the understanding of 🙂 and A little changed what he writes.

But before the program, then you need to install on your computer the Arduino IDE environment – it is free and you can download it here. When launched, it looks more or less like the screenshot below. I marked two points there – the upper right corner is the button to open the “Serial Port Monitor”, while in the lower right corner you can see the connected device (and the port on which you connected).

ArduinoIDE after installation

Now all you need is the program. Mine, after altering the original looks like this:

<Wire.h>#include
const int MPU_ADDR=0x68;
int16_t AcX, AcY, AcZ; acc raw date
int16_t GyX, GyY, GyZ; gyro raw date
Tmp int16_t; temperature date

for calc. roll, pitch and yaw
float accAngleX, accAngleY, gyroAngleX, gyroAngleY, gyroAngleZ;
float elapsedTime, currentTime, previousTime;
float rollZ, pitchX, yawY; Z- looking direction, Y- up/down, X-horizontal through ears
float rotZ, rotX, roty; Z- looking direction, Y- up/down, X-horizontal through ears

varables for calc. error
float AcXerr, AcYerr, AcZerr; acc error
float GyXerr, GyYerr, GyZerr; gyro error
int c;

char tmp_str [7] ; // temporary variable used in convert function
char* convert_int16_to_str(int16_t i) { // convert int16 to string - for debug window
  sprintf(tmp_str, "%6d", i);
  return tmp_str;  
}

void setup() {

start transmission
  Serial.begin(9600);
  Serial.println("setup..");</Wire.h>
  
  put your setup code here, to run once:
  Wire.begin();
  Wire.beginTransmission(MPU_ADDR); Begins a transmission to the I2C slave (GY-521 board)
  Wire.write(0x6B); PWR_MGMT_1 register
  Wire.write(0); set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

do below at the beginning to check the errors that need to go to loop() equations
  comment everything from the loop to have clear output
  calculate_IMU_error(); zero!! GyXerr = 0.12, GyYerr=1.30, GyZerr=0.32;
  delay(500);  
}

void loop() {
  put your main code here, to run repeatedly:
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x3B); starting with reg. 0x38 (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
  Wire.endTransmission(false); the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
  Wire.requestFrom(MPU_ADDR, 7*2, true); request a total of 7 * 2 = 14 registers
  "Wire.read() <8 | Wire.read();" means two registers are read and stored in the same variable
  AcX = Wire.read() <8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() <8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() <8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L)
  Tmp = Wire.read() <8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
  GyX = Wire.read() <8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L)
  GyY = Wire.read() <8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read() <8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L)


  //clclulate useful data
  previousTime = currentTime;        // Previous time is stored before the actual time read
  currentTime = millis();            // Current time actual time read
  elapsedTime = (currentTime - previousTime) / 1000; // Divide by 1000 to get seconds

  // Correct the outputs with the calculated error values
  GyX = GyX + 0.1; // GyroErrorX ~(0.12)
  GyY = GyY + 1.35; // GyroErrorY ~(1.30)
  GyZ = GyZ + 0.32; // GyroErrorZ ~ (0.32)
  
  accAngleX = (atan(AcY / sqrt(pow(AcX, 2) + pow(AcZ, 2))) * 180 / PI) - 0.0; // AccErrorX ~(0.0) See the calculate_IMU_error()custom function for more details
  accAngleY = (atan(-1 * AcX / sqrt(pow(AcY, 2) + pow(AcZ, 2))) * 180 / PI) + 0.0; // AccErrorY ~(0.0)
  gyroAngleX = gyroAngleX + GyX * elapsedTime; // deg/s * s = deg
  gyroAngleY = gyroAngleY + GyY * elapsedTime;
  
  //float rotZ, rotX, rotY; // Z- looking direction, Y- up/down, X-horizontal through ears
  rotY =  (rotY + GyZ * elapsedTime);
  // Complementary filter - combine acceleromter and gyro angle values
  rotZ = (0.96 * gyroAngleX + 0.04 * accAngleX) ;
  rotX = (0.96 * gyroAngleY + 0.04 * accAngleY) ;



  
  // print data
  
  Serial.print("   AcX |    AcY |    AcZ | n"); 
  Serial.print(convert_int16_to_str(AcX)); Serial.print(" | ");
  Serial.print(convert_int16_to_str(AcY)); Serial.print(" | ");
  Serial.print(convert_int16_to_str(AcZ)); Serial.print(" | n");
  // the following equation was taken from the documentation  [MPU-6000/MPU-6050 Register Map and Description, p.30]
  Serial.print("---------------------------n");
  Serial.print(" GyX |    GyY |    GyZ | n"); 
  Serial.print(convert_int16_to_str(GyX)); Serial.print(" | ");
  Serial.print(convert_int16_to_str(GyY)); Serial.print(" | ");
  Serial.print(convert_int16_to_str(GyZ)); Serial.print(" | n");
  Serial.print("---------------------------n");
  Serial.print("Tmp = "); Serial.print(Tmp/340.00+36.53); Serial.print("n");
  Serial.print("---------------------------n");
  Serial.print(" RotX |   RotY |   RotZ | n"); 
  Serial.print(rotX); Serial.print(" | ");
  Serial.print(rotY); Serial.print(" | ");
  Serial.print(rotZ); Serial.print(" | n");
  Serial.print("---------------------------n");

Serial.print("n");
 
delay
  delay(20); 
}

void calculate_IMU_error() {
  We can call this funtion in the setup section to calculate the accelerometer and gyro data error. From here we will get the error values used in the above equations printed on the Serial Monitor.
  Note that we should place the IMU flat in order to get the proper values, so that we then can the correct values
  Read accelerometer values 200 times
  const int NUM_TRIES = 200;
  while (c < NUM_TRIES) {
    Wire.beginTransmission(MPU_ADDR);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_ADDR, 3*2, true);
    AcX = (Wire.read() < 8 | Wire.read()) / 16384.0 ;
    AcY = (Wire.read() < 8 | Wire.read()) / 16384.0 ;
    AcZ = (Wire.read() < 8 | Wire.read()) / 16384.0 ;
    // Sum all readings
    AcXerr = AcXerr + ((atan((AcY) / sqrt(pow((AcX), 2) + pow((AcZ), 2))) * 180 / PI));
    AcYerr = AcYerr + ((atan(-1 * (AcX) / sqrt(pow((AcY), 2) + pow((AcZ), 2))) * 180 / PI));
    c++;
  }
  //Divide the sum by 200 to get the error value
  AcXerr = AcXerr / NUM_TRIES;
  AcYerr = AcYerr / NUM_TRIES;
  c = 0;
  // Read gyro values 200 times
  while (c < NUM_TRIES) {
    Wire.beginTransmission(MPU_ADDR);
    Wire.write(0x43);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_ADDR, 3*2, true);
    GyX = Wire.read() < 8 | Wire.read();
    GyY = Wire.read() < 8 | Wire.read();
    GyZ = Wire.read() < 8 | Wire.read();
    // Sum all readings
    GyXerr = GyXerr + (GyX / 131.0);
    GyYerr = GyYerr + (GyY / 131.0);
    GyZerr = GyZerr + (GyZ / 131.0);
    c++;
  }
  //Divide the sum by 200 to get the error value
  GyXerr = GyXerr / NUM_TRIES;
  GyYerr = GyYerr / NUM_TRIES;
  GyZerr = GyZerr / NUM_TRIES;
  // Print the error values on the Serial Monitor
  Serial.print("AcXerr: ");
  Serial.println(AcXerr);
  Serial.print("AcYerr: ");
  Serial.println(AcYerr);
  Serial.print("GyXerr: ");
  Serial.println(GyXerr);
  Serial.print("GyYerr: ");
  Serial.println(GyYerr);
  Serial.print("GyZerr: ");
  Serial.println(GyZerr);
}

And voual’a! I have readings!

Note to the readings: before I started the main program, I put the sensor still and fired only the calculate_IMU_error() function. It collects 200 readings (this can be changed by setting the value of NUM_TRIES), averages them and shows the result – this is really a sensor error (i.e. kind of a calibration). This error should be included in the program in the calculations:

GyX = GyX + 0.1; GyroErrorX ~(0.12)
GyY = GyY + 1.35; GyroErrorY ~(1.30)
GyZ = GyZ + 0.32; GyroErrorZ ~ (0.32)

accAngleX = (atan(AcY / sqrt(pow(AcX, 2) + pow(AcZ, 2))) * 180 / PI) - 0.0; See the calculate_IMU_error()custom function for more details

accAngleY = (atan(-1 * AcX / sqrt(pow(AcY, 2) + pow(AcZ, 2))) * 180 / PI) + 0.0; AccErrorY ~(0.0)

Readings from GY-251 (MPU6050)

Source:

  1. Documentation of the GY-521 module
  2. Arduino IDE
  3. Program for collecting data from the module with error correction

Leave a Reply

Your email address will not be published. Required fields are marked *