본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Arduino

[IMU] Arduino에서 MPU-9250 사용하는 방법

by zpstls 2023. 1. 19.
반응형

 

 

Arduino에서 IMU Sensor를 한번 사용해 보도록 하겠습니다.

 

우선, IMU에 대해 간단하게 알아보도록 하겠습니다.

IMU는 Inertia Measurement Unit의 약자로 번역하면 관성 측정 장치입니다. 이 Sensor는 어떠한 물체/또는 사람에 부착하여 부착한 Object가 어떠한 움직임을 보이는지 측정합니다.

예를 들면, Motion Capture나 Dron, 비행기 등에 부착되어 널리 사용됩니다. 현대에는 센서가 보편화되면서 웬만한 모든 기기에 기본적으로 부착되는 센서이기도 합니다.

보통 이 Sensor는 6축과 9축으로 나뉘어져있습니다. 6축 센서는 Acceleometer X, Y, Z, Gyroscope X, Y, Z 값을 반환하는 센서이며, 9축 센서는 Acceleometer X, Y, Z, Gyroscope X, Y, Z, Magnet X, Y, Z 값을 반환하는 센서입니다.

6축 센서는 Roll, Pitch 값을 계산할 수 있으며, 9축 센서는 Roll, Pitch, Yaw값을 계산할 수 있습니다. 그럼 이 값을 가지고 뭘 하는 것일까요?

Dron의 경우 Dron 자체의 중심을 잡기 위해서 Pitch와 Roll 값을 이용해야 합니다. 즉, 중심을 잡기 위한 Data로는 Roll과 Pitch 값만 있으면 됩니다. 하지만 여기서 더 나아가 Dron이 어느 방향을 향해 이동하는지까지 알기 위해서는 Yaw값이 추가로 필요합니다.

필요한 Data에 따라 6축 센서를 사용할지 9축 센서를 사용할지 결정해 사용하면 됩니다. 하지만 보통 두 센서 사이의 가격차이는 크게 없기에 9축 센서를 사용하는 것이 좋긴 하겠지요.

그렇다면 이 IMU 센서의 원리는 무엇일까요? 먼저 Acceleometer는 지구의 중력가속도를 기준으로 사물이 얼마만큼의 힘을 받고 있는지 측정하는 것입니다. 시간 경과와 관계없이 오차에 강하지만 지표면에 수직인 면에 대해 회전하는 각(= 방위각)은 측정할 수 없습니다. 또한 물체의 움직임에 따른 가속도 발생과 기본적으로 부여되는 중력 가속도 간의 구분이 불가능합니다.

보통 Roll, Pitch 값을 구할 수 있으나 물체 자체의 움직임에 의한 오차가 발생하기에 Acceleometer 만으로는 IMU센서를 구성할 수는 없습니다. Acceleometer를 보완하기 위해 Gyroscope가 함께 사용되며, Gyroscope는 코리올리의 힘(= 회전계에서 느껴지는 관성력)이 일한다는 물리현상을 이용해 각속도(= 특정 축을 기준으로 각이 돌아가는 속력)를 측정하는 것입니다. 방위각과 모든 축에 대한 회전각 정보를 제공합니다. 하지만 시간이 지남에 따라 적분 계산으로 인한 오차가 발생하며 오차가 축적되면 Drift현상이 발생됩니다. 또한 온도 변화에 따라 변하는 특성이 있어 IMU 센서 모듈에 온도 센서가 포함되어 제공되기도 합니다.

위와 같이 전통적인 Gyroscope는 팽이처럼 생겼으며, 현대에는 전기적 성질을 이용해 Gyroscope를 구성합니다. 팽이가 회전을 하다가 시간이 지남에 따라 중력가속도 힘에 의해 원과 같은 형태를 띠며 멈추는 현상(= 세차 운동)을 이용해 각속도 X, Y, Z Vector값을 계산합니다.

마지막으로 Magnet 센서를 이용해 Yaw값을 추가적으로 구현할 수 있습니다. Magnet은 자기 저항 효과(= MR 효과), 홀 효과 등을 이용해 자기장의 방향 및 속도를 측정합니다.

 

Magnetoresister Effect(= MR)는 자기장에 따라 반도체 또는 금속 재료의 저항이 변화하는 현상으로 자기장과 저항의 관계로부터 지자기를 유추합니다. Hall Effect는 홀 전압(= 자기장의 수직 한 방향으로 전류를 인하여 자기장과 전류의 방향에 수직인 방향으로 전압 발생)을 측정하여 자기장을 유추하는 방식입니다. Magneto Impedance(= MI)는 특수한 아몰퍼스 와이어라는 것을 사용해 그 자기 임피던스 효과를 응용하는 방식이라고 합니다. 보통 성능이 좋은 Magnet 센서는 MI를 이용해 제작한다고 합니다.

 

최종적으로 정리하면, 정지 상태의 긴 관점에서는 가속도 센서에 의해 계산된 기울어진 각도는 올바른 값의 양상을 보이게 되겠지만 자이로센서는 시간이 지날수록 틀린 값을 나타낼 것입니다. 움직이는 짧은 시간의 관점에서는 자이로센서는 올바른 값을 보여줄 것이나 가속도 센서는 기울어진 각도와는 관계없는 값을 반환할 것입니다. 마지막으로 Yaw값을 측정함에 있어서는 가속도 센서만의 사용은 의미가 없으며 자이로센서는 z축값을 측정하고 이 값을 이용해 Yaw값을 계산할 수는 있으나 Drift 되는 값을 보상하는 다른 센서, 즉 지자기 센서를 이용해야 올바른 Yaw값을 측정할 수 있습니다.

 

 

 

 

 

IMU 센서 중 가장 널리 사용되는 것은 MPU Series입니다. 6축 센서로는 MPU-6050, 9축 센서로는 MPU-9250입니다. 해당 센서들은 I2C 통신을 이용해 Board에서 Sensor로 접근할 수 있습니다. 그럼 이 I2C 통신은 무엇일까요?

I2C는 Inter-Integrated Circuit의 약자로 SDA라는 데이터를 주고받기 위한 선과 SCL이라는 송/수신 타이밍 동기화를 위한 Clock선으로 구성되어 있습니다.

위 이미지를 기준으로 보면 SDA, SCL 선에 각각의 n개의 Device들이 연결되어 있으며 송신과 수신이 구분된 통신을 합니다. 즉 2개의 선으로 여러 대의 기기와 연결해 송신 또는 수신의 통신을 할 수 있습니다.

Device들 중 하나가 Master, 나머지 Device들은 Slave입니다. Master는 SCL을 생성하고 이 Clock 신호에 맞춰 Data를 송/수신합니다. Slave는 개별 Address를 가지게 되며 이 Address는 식별 ID입니다. Master는 SCL, SDA를 이용해 Data를 전달하게 되는데, Slave의 Address정보도 같이 전달합니다. 즉 Master에 의해 선택된 Slave만 Data를 받을 수 있습니다.

I2C 관련 Library는 Arduino IDE 기준으로 #include <Wire.h>를 이용해 사용할 수 있습니다.

해당 라이브러리는 Arduino IDE에서 다음과 같이 설치할 수 있습니다.

Arduino IDE를 실해하고 위쪽 메뉴에서 [스케치]->[라이브러리 포함하기]->[라이브러리 관리…]를 누릅니다. 이후 "wire"를 검색하고 상단에 나오는 Wire Built-In을 Install 합니다. install이 끝나면 <Wire.h>를 사용할 수 있습니다.

 

 

 

 

 

이제, 본격적으로 Hardware를 구성하고 Software를 개발해보도록 하겠습니다. Arduino Nano와 MPU-9250을 사용할 것입니다.

다음과 같이 Arduino Nano와 MPU-9250을 연결할 수 있습니다.

사용되는 Board의 종류에 따라 Pin Map이 다름으로 해당 Board에 맞는 Map을 참고해 연결하면 됩니다. Arduino Nano를 기준으로 MPU-9250의 VCC 및 GND는 Nano의 해당 Pin에 각각 연결하고 MPU-9250의 SDA Pin은 Nano의 27번 Pin, SCL은 28번 Pin에 연결합니다.

MPU-9250은 Interrupt 기능도 제공하니 필요하다면 INT Pin을 Nano의 32번 또는 0번 Pin에 연결해 사용할 수 있습니다.

 

Hardware적으로 준비가 완료되었다면 다음과 같은 Code를 이용해 MPU-9250의 Raw Data를 Read 할 수 있습니다. MPU-9250의 Datasheet에 따른 Address에 따라 해당 Data를 요청하고 읽는 방식으로 Code가 구현되어 있습니다.

더보기

MPU-9250 Code

#include <Wire.h>
#include <TimerOne.h>

#define    MPU9250_ADDRESS            0x68
#define    MAG_ADDRESS                0x0C

#define    GYRO_FULL_SCALE_250_DPS    0x00  
#define    GYRO_FULL_SCALE_500_DPS    0x08
#define    GYRO_FULL_SCALE_1000_DPS   0x10
#define    GYRO_FULL_SCALE_2000_DPS   0x18

#define    ACC_FULL_SCALE_2_G        0x00  
#define    ACC_FULL_SCALE_4_G        0x08
#define    ACC_FULL_SCALE_8_G        0x10
#define    ACC_FULL_SCALE_16_G       0x18

void I2Cread(uint8_t Address, uint8_t Register, uint8_t Nbytes, uint8_t* Data)
{
  // Set register address
  Wire.beginTransmission(Address);
  Wire.write(Register);
  Wire.endTransmission();
  
  // Read Nbytes
  Wire.requestFrom(Address, Nbytes); 
  uint8_t index=0;
  while (Wire.available())
  Data[index++]=Wire.read();
}

void I2CwriteByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
  // Set register address
  Wire.beginTransmission(Address);
  Wire.write(Register);
  Wire.write(Data);
  Wire.endTransmission();
}

// Initial time
long int ti;
volatile bool intFlag=false;

// Initializations
void setup()
{
  // Arduino initializations
  Wire.begin();
  Serial.begin(115200);
  
  // Set accelerometers low pass filter at 5Hz
  I2CwriteByte(MPU9250_ADDRESS,29,0x06);
  // Set gyroscope low pass filter at 5Hz
  I2CwriteByte(MPU9250_ADDRESS,26,0x06);
   
  // Configure gyroscope range
  I2CwriteByte(MPU9250_ADDRESS,27,GYRO_FULL_SCALE_1000_DPS);
  // Configure accelerometers range
  I2CwriteByte(MPU9250_ADDRESS,28,ACC_FULL_SCALE_4_G);
  // Set by pass mode for the magnetometers
  I2CwriteByte(MPU9250_ADDRESS,0x37,0x02);
  
  // Request continuous magnetometer measurements in 16 bits
  I2CwriteByte(MAG_ADDRESS,0x0A,0x16);
  
  Timer1.initialize(10000);         // initialize timer1, and set a 1/2 second period
  Timer1.attachInterrupt(callback);  // attaches callback() as a timer overflow interrupt
    
  // Store initial time
  ti=millis();
}

void loop()
{
  while (!intFlag);
  intFlag=false;
  
  // Display time
  Serial.print (millis()-ti,DEC);
  Serial.print ("\t");  
  
  // ____________________________________
  // :::  accelerometer and gyroscope ::: 

  // Read accelerometer and gyroscope
  uint8_t Buf[14];
  I2Cread(MPU9250_ADDRESS,0x3B,14,Buf);
      
  // Accelerometer
  int16_t ax=-(Buf[0]<<8 | Buf[1]);
  int16_t ay=-(Buf[2]<<8 | Buf[3]);
  int16_t az=Buf[4]<<8 | Buf[5];

  // Gyroscope
  int16_t gx=-(Buf[8]<<8 | Buf[9]);
  int16_t gy=-(Buf[10]<<8 | Buf[11]);
  int16_t gz=Buf[12]<<8 | Buf[13];
    
  // Accelerometer - display
  Serial.print (ax,DEC); 
  Serial.print ("\t");
  Serial.print (ay,DEC);
  Serial.print ("\t");
  Serial.print (az,DEC);  
  Serial.print ("\t");
  
  // Gyroscope - display
  Serial.print (gx,DEC); 
  Serial.print ("\t");
  Serial.print (gy,DEC);
  Serial.print ("\t");
  Serial.print (gz,DEC);  
  Serial.print ("\t");

  
  // _____________________
  // :::  Magnetometer ::: 
  uint8_t ST1;
  do
  {
    I2Cread(MAG_ADDRESS,0x02,1,&ST1);
  }
  while (!(ST1&0x01));

  // Read magnetometer data  
  uint8_t Mag[7];  
  I2Cread(MAG_ADDRESS,0x03,7,Mag);
  
  // Magnetometer
  int16_t mx=-(Mag[3]<<8 | Mag[2]);
  int16_t my=-(Mag[1]<<8 | Mag[0]);
  int16_t mz=-(Mag[5]<<8 | Mag[4]);
    
  // Magnetometer
  Serial.print (mx+200,DEC); 
  Serial.print ("\t");
  Serial.print (my-70,DEC);
  Serial.print ("\t");
  Serial.print (mz-700,DEC);  
  Serial.print ("\t");
    
  // End of line
  Serial.println("");    
}

위 Code를 수행하면 다음과 같은 결과를 얻을 수 있습니다.

맨 왼쪽은 Time을 나타내며, 이후 오른쪽 순으로 Acceleometer X, Acceleometer Y, Acceleometer Z, Gyroscope X, Gyroscope Y, Gyroscope Z, Magnet Z, Magnet Y, Magnet Z입니다.

해당 Data는 Raw Data로서 오차가 심한 Data입니다. 따라서 이 Data들을 올바르게 사용하기 위해서는 오차를 보정해 줄 수 있는 Filter를 사용해주어야 합니다. 많이 사용하는 Filter로는 Complementary Filter, Kalman Filter, Madgwick Filter 등이 있습니다.

 

 

 

 

 

앞서 오차를 보정하기 위한 Filter를 Raw Data에 적용해 주어야 한다고 하였습니다. Filter들 중 가장 간단한 Complementary Filter에 대해 간략하게 알아보도록 하겠습니다. 번역하면 상보 필터라고 하며 이는 6축 센서에서 많이 사용되는 Filter입니다.

상보 필터의 공식은 다음과 같습니다.

가속도센서 Data에는 0.05라는 비교적 작은 값이 곱해지며 [자이로센서 값+이전 값]에는 0.95라는 비교적 큰 값이 곱해집니다. 이는 가속도 값을 상대적으로 약화시키고 [자이로센서값+이전 값]을 더 신뢰한다는 의미입니다. 즉 순간적으로 변하게 되는 가속도 센서의 값을 약화시키는 것이지요. 이때 가속도 값의 영향력을 약화시켰다고 하더라도 지속적으로 영향을 끼칠 것이기에 가속도 센서 값으로 인해 변화는 꾸준히 발생하게 될 것입니다. 따라서 상보 필터는 각 센서들 간의 밸런스는 어느 정도 맞추면서 불리한 값을 약화시키는 역할을 합니다.

모든 Filter들을 기본적으로 상보필터와 같은 역할을 합니다. 다만 Filter마다 성능이 조금씩 다르겠지요. 실제 적용할 Filter는 Madgwick의 Filter입니다. 계산 속도가 빠르면서 센서의 오차를 줄이는 성능이 좋다고 합니다.

 

 

이로서 IMU 센서가 무엇인지, 어떤 원리인지, 그리고 이를 어떻게 이용하는지에 대해 알아보았습니다.

IMU 센서는 많이 사용되는 센서인 만큼 진입장벽이 조금 낮습니다. 하지만 측정되는 Data를 Filtering 하는 부분은 꽤 어려운 작업입니다. 실제 내가 원하는 효과를 내지 못하는 경우도 많고 수학적인 부분이다 보니 어려운 부분도 있습니다.

하지만 한 번 구현해 놓으면 활용 시 많이 도움이 되는 작업이기도 합니다.

 

이번 포스팅은 여기서 마무리하도록 하겠습니다.

 

 

반응형

댓글