카테고리 없음

MPU6050(GY-521) 이해하기 / Angle Z 값이 제대로 나오지 않을 때(MPU 92/65, GY-6500도 작동함)

미친토끼 2025. 5. 19. 12:20

https://mschoeffler.com/2017/10/05/tutorial-how-to-use-the-gy-521-module-mpu-6050-breakout-board-with-the-arduino-uno/

 

Tutorial: How to use the GY-521 module (MPU-6050 breakout board) with the Arduino Uno

This tutorial teaches how to use the GY-521 Module (MPU-6050 MEMS). The tutorial covers wiring as well as programming of the GY-521 module.

mschoeffler.com

여기에 나오는 코드를 실행시켜 보면, 다른 값들은 잘 나오는데, 유독 aZ값만 제대로 나오지 않는다. 기판을 움직여보면 다른 값들은 -16000에서 16000로 왔다갔다하는데, 유독 aZ만 18426 식으로 18000에서 움직일 요량을 하지 않는다.

알아 보니, aZ값은 다른 값들에 기초해 계산을 해야 한단다. 

https://stackoverflow.com/questions/19436706/filter-z-angle-rotation-mpu-6050-arduino

여기에 보면 답변이 나와 있다.

모든 축들의 회전을 계산한 다음, 그것에 기초헤 Z축 회전값을 계산해야 한단다. 그리고 칩을 가만히 두어도 좌표값이 변하는 것은, 칩이 움직이지 않을 때의 출력값의 평균을 계산해서, 그 값을, 칩이 움직일 때의 출력값에서 빼줘야 한다고 한다. 이것이 칼리브레이션이라고 한다.

 

아래의 설명이 아주 좋은데, 여기에 나오는 코드를 실행시켜보면, 우리가 아는 360도 식으로 각도를 잘 출력해준다. registers의 값을 읽어오는 각도 관련 raw데이터는, 각도와 속도를 뽑아내는 것의 기반이 될 뿐이지, 그것 자체가 각도가 아님을 명심할 필요가 있다.

 

https://medium.com/@kavindugimhanzoysa/lets-work-with-mpu6050-gy-521-part1-6db0d47a35e6

 

Lets work with MPU6050 (GY-521) — Part1

In this part I am mainly discussing the basic functionalities of MPU6050 and how to get the angular velocity and angular position using…

medium.com

 

MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2

https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf

 

 

#include <Wire.h>

long accelX, accelY, accelZ;
float gForceX, gForceY, gForceZ;

long gyroXCalli = 0, gyroYCalli = 0, gyroZCalli = 0;
long gyroXPresent = 0, gyroYPresent = 0, gyroZPresent = 0;
long gyroXPast = 0, gyroYPast = 0, gyroZPast = 0;
float rotX, rotY, rotZ;

float angelX = 0, angelY = 0, angelZ = 0;

long timePast = 0;
long timePresent = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  setUpMPU();
  callibrateGyroValues();
  timePresent = millis();
}

void loop() {
  //Serial.print(" Accel (g)");
  readAndProcessAccelData();
  readAndProcessGyroData();
  printData();
  delay(100);
}

void setUpMPU() {
  // power management
  Wire.beginTransmission(0b1101000);          // Start the communication by using address of MPU
  Wire.write(0x6B);                           // Access the power management register
  Wire.write(0b00000000);                     // Set sleep = 0
  Wire.endTransmission();                     // End the communication

  // configure gyro
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1B);                           // Access the gyro configuration register
  Wire.write(0b00000000);
  Wire.endTransmission();

  // configure accelerometer
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1C);                           // Access the accelerometer configuration register
  Wire.write(0b00000000);
  Wire.endTransmission();  
}

void callibrateGyroValues() {
    for (int i=0; i<5000; i++) {
      getGyroValues();
      gyroXCalli = gyroXCalli + gyroXPresent;
      gyroYCalli = gyroYCalli + gyroYPresent;
      gyroZCalli = gyroZCalli + gyroZPresent;
    }
    gyroXCalli = gyroXCalli/5000;
    gyroYCalli = gyroYCalli/5000;
    gyroZCalli = gyroZCalli/5000;
}

void readAndProcessAccelData() {
  Wire.beginTransmission(0b1101000); 
  Wire.write(0x3B); 
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); 
  while(Wire.available() < 6);
  accelX = Wire.read()<<8|Wire.read(); 
  accelY = Wire.read()<<8|Wire.read(); 
  accelZ = Wire.read()<<8|Wire.read(); 
  processAccelData();
}

void processAccelData() {
  gForceX = accelX/16384.0;
  gForceY = accelY/16384.0; 
  gForceZ = accelZ/16384.0;
}

void readAndProcessGyroData() {
  gyroXPast = gyroXPresent;                                   // Assign Present gyro reaging to past gyro reading
  gyroYPast = gyroYPresent;                                   // Assign Present gyro reaging to past gyro reading
  gyroZPast = gyroZPresent;                                   // Assign Present gyro reaging to past gyro reading
  timePast = timePresent;                                     // Assign Present time to past time
  timePresent = millis();                                     // get the current time in milli seconds, it is the present time
  
  getGyroValues();                                            // get gyro readings
  getAngularVelocity();                                       // get angular velocity
  calculateAngle();                                           // calculate the angle  
}

void getGyroValues() {
  Wire.beginTransmission(0b1101000);                          // Start the communication by using address of MPU 
  Wire.write(0x43);                                           // Access the starting register of gyro readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6);                              // Request for 6 bytes from gyro registers (43 - 48)
  while(Wire.available() < 6);                                // Wait untill all 6 bytes are available
  gyroXPresent = Wire.read()<<8|Wire.read();                  // Store first two bytes into gyroXPresent
  gyroYPresent = Wire.read()<<8|Wire.read();                  // Store next two bytes into gyroYPresent
  gyroZPresent = Wire.read()<<8|Wire.read();                  //Store last two bytes into gyroZPresent
}

void getAngularVelocity() {
  rotX = gyroXPresent / 131.0;                                
  rotY = gyroYPresent / 131.0; 
  rotZ = gyroZPresent / 131.0;
}

void calculateAngle() {  
  // same equation can be written as 
  // angelZ = angelZ + ((timePresentZ - timePastZ)*(gyroZPresent + gyroZPast - 2*gyroZCalli)) / (2*1000*131);
  // 1/(1000*2*131) = 0.00000382
  // 1000 --> convert milli seconds into seconds
  // 2 --> comes when calculation area of trapezium
  // substacted the callibated result two times because there are two gyro readings
  angelX = angelX + ((timePresent - timePast)*(gyroXPresent + gyroXPast - 2*gyroXCalli)) * 0.00000382;
  angelY = angelY + ((timePresent - timePast)*(gyroYPresent + gyroYPast - 2*gyroYCalli)) * 0.00000382;
  angelZ = angelZ + ((timePresent - timePast)*(gyroZPresent + gyroZPast - 2*gyroZCalli)) * 0.00000382;
}

void printData() {
  Serial.print("Gyro (deg/sec)");
  Serial.print(" X=");
  Serial.print(rotX);
  Serial.print(" Y=");
  Serial.print(rotY); 
  Serial.print(" Angle (deg)");
  Serial.print(" Z=");
  Serial.print(rotZ);

  //Serial.println("Angular displacement wrt started position (deg)");
  Serial.print("  X=");
  Serial.print(angelX);
  Serial.print(" Y=");
  Serial.print(angelY);
  Serial.print(" Z=");
  Serial.print(angelZ);
    
  Serial.print("  Acceleration (g)");
  Serial.print(" X=");
  Serial.print(gForceX);
  Serial.print(" Y=");
  Serial.print(gForceY);
  Serial.print(" Z=");
  Serial.println(gForceZ);
}

 

MPU92/65도 저렴한 맛에 샀는데 헐~ 이것도 MPU6050 코드가 아무런 이상없이 잘 돌아가네요. register map 메뉴얼을 보니, MPU6050과 데이터 레지스터는 동일해 보이는데, 데이터 통신에는 별 차이가 없고, 핀 수가 더 많아서 거기서 비롯된 기능상 차이가 좀 있는가 보다. MPU6050이 2천원 정도, MPU92/65가 천원 남짓하는데, 이거 봉잡았네...

한줄에 결과가 다 출력되도록 원래 소스를 조금만 수정했는데, 결과는 MPU92/65와 MPU6050이 동일해 보인다. 칩을 장착한 브레드 보드를 움직이면 가운데의 X, Y, Z값이 변한다. roll, pitch, yaw 순서든가... 아무튼...

 

(흥분을 가라앉히고 두어 시간 동안 침착하게 확인해보니, 저렴이 MPU92/65는 GY6500 (MPU6500) 6축 센서이고, 위의 RAW 데이타와 가공 데이터 추출 코드는, 의미는 있으나 실용적이지는 않아서 상당히 부정확하다. 이런저런 라이브러리로 테스트해보고 있는데, 현재로선 MPU6050_tockn이 제법 쓸만하다. angleX, angleY는 확실히 잘 뽑아내고 흔들림이 없다. 다만 angleZ는 몇 번 회전하다보면 전혀 엉뚱한 값을 내놓는데, 라이브러리 제작자의 말을 들어보니, 6축 센서에서 angleZ를 정확하게 뽑아낼 방법은 없다고 한다. 보다 정확한 값을 원한다면 magnetic 기능이 있는 9축 센서를 사용하라고 한다. 가령 MPU9265... 가격은 만원이 넘어간다. 수평을 잡는 선에서 만족해야겠다. 수평 잡은 상태에서 방향까지 잡기를 바라는 것은 욕심인 듯... MPU6050에 나침반을 달면 방향을 알 수 있지 않을까? 그게 magnetic을 갖춘 9축 센서를 말하는 듯하다.

MPU6050 센서 3개와, MPU 92/65 센서 하나를 가지고 있는데, 아주 잘 돌아간다. 오늘은 센서 데이터를 ROW로 읽는 소스 하나, 그것을 변환하여 각도 및 g 값으로 출력하는 코드 하나를 이해하는 데 주력했다. 대성공! MPU 92/65도 라이브러리를 사용하지 않고 그냥 ROW 데이터를 가공하는 소스를 사용해서인지 MPU6050용 소스가 아무런 이상없이 돌아간다. map register 문서를 보니 데이터가 수록된 레지스터 주소가 MPU6050과 동일하다!! 그런데 아두이노 나노에서는 어쩐지 이 센서들을 인식하지 못한다. ESP32와 라즈베리파이에서는 어떻게 될지 궁금하다.