MPU6050 센서는 가속도계(Accelerometer)와 자이로(Gyroscope)가 1개의 칩에 모두 포함하고 있는 6DOF(Degrees of Freedom) MEMS(Micro Electro Mechanical Systems) 센서로, I2C(Inter Integrated Circuit) 통신 프로토콜을 통해서 데이터를 가져올 수 있습니다. 다음은 MPU6050의 메뉴얼입니다.


http://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf



MPU6050의 특징입니다.

      • 3-axis Accel + 3-axis Gyroscope DMP(Digital Motion Processor)

      • ±1% Temperature Sensor( Digital output )

      • 7개의 16bit ADC를 내장하여 16bit 정교한 기울기 출력

      • ±250,±500,±1000,±2000(˚/sec) dps 자이로, ±2,±4,±8,±16g 가속도 (User programable)

      • 1024byte fifo buffer

      • I2C 400KHz

      • Programable Interrupts

      • High-G Interrupts

      • VDD: 2.375V ~ 3.46V

      • Gyroscope Operating current 3.6mA( Standby 5uA )

      • Accelerometer Operating current 500uA( Low Power mode 10uA~ )

      • Programable Low-Pass Filter

      • -40℃ ~ +85℃ (TA 25℃ )

      • StartUp time 30msec

      • Self Test

      • I2C Address : 0x68 ( except R/W 0x1 )

      • I2C Master or Slave

      • Auxiliary I2C bus for communicating to an off-chip 3-Axis digital output magnetometer(지자계 센서) or other sensors.


MPU6050은 24pin QFN 패키지로 내부 timing generator를 사용하지 않는 경우 외장 32.768kHz 혹은 19.2MHz 클럭이 필요합니다. 따라서 사용이 용이하도록 주변 소자를 내장한 MPU6050의 Breakout 보드가 있으며 GY-521의 그 중의 하나입니다.


GY-521의 회로도


GY-521 모듈

(가속도계와 자이로의 방향은 MPU6050 칩의 1번 핀을 기준으로 3차원 축이 결정되며 MPU6050과 동일함을 알 수 있습니다)


MPU6050(GY-521)은 가속도계를 포함하므로 가속도를 측정하는 센서는 아닙니다. 단지 가속도를 이용하여 3차원 공간상 X, Y 그리고 Z 축을 중심으로 기울어진 각도(기울기)를 얻는 센서입니다. 가속도는 중력 방향과의 반대 방향일 때 양(+)이고 아래에서 각 축에 곡선 화살표는 자이로의 회전방향을 나타냅니다. 가속도계와 자이로 외에도 온도도 측정할 수 있는데 이는 이와같은 센서가 온도에 따라 약간 변화하기 때문에 이를 보정하는 목적으로 제공합니다.


만일 MPU6050을 비행기에 탑재하고 진행방향이 Y축 방향과 같다면 가속도계 출력 AccX는 롤(Roll), AccY는 피치(Pitch)그리고 AccZ는 요(Yaw)가 됩니다. 즉 AccX는 X축을 기준으로 기울어진 각도를 의미합니다. 가속도계는 X, Y축에 대해서 기울어진 정도를 중력가속도[g]의 단위로 출력합니다. 이때 기준 방향은 중력방향입니다. 그러나 Z축이 중력방향이 일치하는 경우 요를 구할 수 없다는 것입니다.


만일 움직이지 않고 이동하는 경우 진행 방향의 가속도의 영향으로 중력 방향이 변하게 되어 부정확하게 된다는 것입니다. 이러한 이유로 자이로의 측정 결과를 참조하게 되는데, 자이로는 짧은 시간은 정확하기 때문입니다. 하지만 긴 시간에 대해서는 자이로 센서가 측정시 함유하는 잡음 등을 각속도를 적분하여 기울기를 얻기 때문에 적분하는데, 이 과정에서 오차(적분상수)는 누적되고 시간에 따라 자이로 측정값은 드리프트하게 됩니다. 이때 변화분을 bias라고도 부릅니다.



위 그림에서 X축을 중심으로 회전한 각도 φ와 Y축을 중심으로 회전한 각도 ρ의 계산식입니다. 만일 X축 자체가 기울어지지 않았다면, 중력이 X축 상에 기여도는 없어 φ는 arctan(Ay/Az)으로 간략하게 됩니다. 여기서 Ax, Ay, Az는 AccX, AccY, AccZ입니다. ρ에서 음의 부호는 X축 중심으로 회전각도는 Y축이 위쪽으로 기울어져야 양이지만, Y축 중심으로 회전각도는 X축이 아래로 기울어져야 양이기 때문입니다.


MPU6050을 사용해 실시간으로 기울기를 요구하는 시스템은 가속도계의 측정값과 자이로의 측정값을 적절히 잡음을 고려하여 융합하고 최적의 가장 정확한 기울기를 얻어냅니다. 이때 사용하는 필터는 보상필터(Complementary filter; 혹은 상보필터)와 칼만필터(Kalman filter)로 알려집니다.


MPU6050은 7개의 채널에 대해서 16bit 크기의 값을 출력해주는 고성능 ADC를 내장하므로 각 축의 센서 출력값에 대해서 int16_t(-32768~32767)의 자료형으로 접근해야 합니다. 또한 MPU6050은 update rate(sampling time)이 가속도, 자이로에 대해서 각각 4~1000Hz, 4~8000Hz으로 출력값을 제공합니다.


MPU6050은 내부 레지스터를 이용해서 출력 값의 범위를 조정할 수 있습니다. 예를 들어 가속도계에서 AFS_SEL=0으로 설정함으로써 출력은 ±2[g]까지 나타낼 수 있으며 이를 2byte 크기로 나타내게 됩니다. 만일 AFS_SEL=0과 FS_SEL=0을 설정하였다면 다음과 같습니다.



가속도계에서는 최대 ±2[g]이고(-2g에서 +2g까지 측정하여 -32768에서 +32767까지 매핑Scale Factor가 1g당 16,384로 출력에 이를 나누어주면 실제 [g] 단위를 얻을 수 있습니다. 그러나 우리가 원하는 것은 기울어지 각도이므로 arctan에서는 비율(ratio)만을 사용하므로 단위는 의미가 없게 됩니다. 자이로에서는 최대 ±250[deg/s]이고(-250에서 +250까지 측정하여 -32768에서 32767까지 매핑Scale Factor가 131(32767/250)로 출력에 이를 나누어주면 실제 [deg/s] 단위의 각가속도를 얻을 수 있습니다. 각 센서는 감도(Sensitivity)를 증가시킬수록 미세하게 측정 가능하지만 정확도는 떨어집니다.


다음은 아두이노(Arduino) 보드와 GY-521(MPU6050) 모듈과의 연결 방법과 Wire 라이브러리를 이용하여 실행한 MPU6050의 데이터 출력의 예제입니다.


아두이노 보드와 연결 방법



// MPU-6050 Short Example Sketch
// By Arduino User JohnChi
// August 17, 2014
// Public Domain

#include<Wire.h>
const int MPU_addr=0x68;  // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
void setup(){
  Wire.begin(); // Wire 라이브러리 초기화
  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)
  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.print(AcZ);
  Serial.print(" | Tmp = "); Serial.print(Tmp/340.00+36.53);  //equation for temperature in degrees C from datasheet
  Serial.print(" | GyX = "); Serial.print(GyX);
  Serial.print(" | GyY = "); Serial.print(GyY);
  Serial.print(" | GyZ = "); Serial.println(GyZ);
  delay(333); 
}




'Flight Controller 이해 > 센서' 카테고리의 다른 글

관성측정장치(IMU)의 원리  (0) 2017.03.14
드론에 요구되는 각종 센서들  (0) 2017.02.26
Posted by Nature & Life
Embedded Lecture/Arduino2017. 11. 30. 18:04


아두이노에서 기본적으로 제공하는 Wire 라이브러리를 이용해도 되지만 I2C 라이브러리를 이용하여 MPU6050의 X축, Y축 그리고 Z축의 기울기를 구하였습니다.


i2c.ino

const uint8_t IMUAddress = 0x68; // AD0 is logic low on the PCB

const uint16_t I2C_TIMEOUT = 1000; // Used to check for errors in I2C communication


uint8_t i2cWrite(uint8_t registerAddress, uint8_t data, bool sendStop) {

  return i2cWrite(registerAddress,&data,1,sendStop); // Returns 0 on success

}


uint8_t i2cWrite(uint8_t registerAddress, uint8_t* data, uint8_t length, bool sendStop) {

  Wire.beginTransmission(IMUAddress);

  Wire.write(registerAddress);

  Wire.write(data, length);

  return Wire.endTransmission(sendStop); // Returns 0 on success

}


uint8_t i2cRead(uint8_t registerAddress, uint8_t* data, uint8_t nbytes) {

  uint32_t timeOutTimer;

  Wire.beginTransmission(IMUAddress);

  Wire.write(registerAddress);

  if(Wire.endTransmission(false)) // Don't release the bus

    return 1; // Error in communication

  Wire.requestFrom(IMUAddress, nbytes,(uint8_t)true); // Send a repeated start and then release the bus after reading

  for(uint8_t i = 0; i < nbytes; i++) {

    if(Wire.available())

      data[i] = Wire.read();

    else {

      timeOutTimer = micros();

      while(((micros() - timeOutTimer) < I2C_TIMEOUT) && !Wire.available());

      if(Wire.available())

        data[i] = Wire.read();

      else

        return 2; // Error in communication

    }

  }

  return 0; // Success

}


Kalman.h

/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics-> All rights reserved->

 This software may be distributed and modified under the terms of the GNU

 General Public License version 2 (GPL2) as published by the Free Software

 Foundation and appearing in the file GPL2->TXT included in the packaging of

 this file-> Please note that GPL2 Section 2[b] requires that all works based

 on this software must also be made publicly available under the terms of

 the GPL2 ("Copyleft")->


 Contact information

 -------------------

 Kristian Lauszus, TKJ Electronics

 Web      :  http://www->tkjelectronics->com

 e-mail   :  kristianl@tkjelectronics->com

 */


#ifndef _Kalman_h

#define _Kalman_h


struct Kalman {

    /* Kalman filter variables */

    double Q_angle; // Process noise variance for the accelerometer

    double Q_bias; // Process noise variance for the gyro bias

    double R_measure; // Measurement noise variance - this is actually the variance of the measurement noise


    double angle; // The angle calculated by the Kalman filter - part of the 2x1 state vector

    double bias; // The gyro bias calculated by the Kalman filter - part of the 2x1 state vector

    double rate; // Unbiased rate calculated from the rate and the calculated bias - you have to call getAngle to update the rate


    double P[2][2]; // Error covariance matrix - This is a 2x2 matrix

    double K[2]; // Kalman gain - This is a 2x1 vector

    double y; // Angle difference

    double S; // Estimate error

};


void Init(struct Kalman* klm){

    /* We will set the variables like so, these can also be tuned by the user */

    klm->Q_angle = 0.001;

    klm->Q_bias = 0.003;

    klm->R_measure = 0.03;


    klm->angle = 0; // Reset the angle

    klm->bias = 0; // Reset bias

   klm->P[0][0] = 0; // Since we assume that the bias is 0 and we know the starting angle (use setAngle), the error covariance matrix is set like so - see: http://en->wikipedia->org/wiki/Kalman_filter#Example_application->2C_technical

    klm->P[0][1] = 0;

    klm->P[1][0] = 0;

    klm->P[1][1] = 0;

}


// The angle should be in degrees and the rate should be in degrees per second and the delta time in seconds

double getAngle(struct Kalman * klm, double newAngle, double newRate, double dt) {

    // KasBot V2  -  Kalman filter module - http://www->x-firm->com/?page_id=145

    // Modified by Kristian Lauszus

   // See my blog post for more information: http://blog->tkjelectronics->dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it


    float P00_temp;

    float P01_temp;


    // Discrete Kalman filter time update equations - Time Update ("Predict")

    // Update xhat - Project the state ahead


   /* Step 1 */

    klm->rate = newRate - klm->bias;

    klm->angle += dt * klm->rate;


    // Update estimation error covariance - Project the error covariance ahead


    /* Step 2 */

    klm->P[0][0] += dt * (dt*klm->P[1][1] - klm->P[0][1] - klm->P[1][0] + klm->Q_angle);

    klm->P[0][1] -= dt * klm->P[1][1];

    klm->P[1][0] -= dt * klm->P[1][1];

    klm->P[1][1] += klm->Q_bias * dt;


    // Discrete Kalman filter measurement update equations - Measurement Update ("Correct")

    // Calculate Kalman gain - Compute the Kalman gain


    /* Step 4 */

    klm->S = klm->P[0][0] + klm->R_measure;


    /* Step 5 */

    klm->K[0] = klm->P[0][0] / klm->S;

    klm->K[1] = klm->P[1][0] / klm->S;


    // Calculate angle and bias - Update estimate with measurement zk (newAngle)


    /* Step 3 */

    klm->y = newAngle - klm->angle;

 

   /* Step 6 */

    klm->angle += klm->K[0] * klm->y;

    klm->bias += klm->K[1] * klm->y;


    // Calculate estimation error covariance - Update the error covariance


    /* Step 7 */

    P00_temp = klm->P[0][0];

    P01_temp = klm->P[0][1];


    klm->P[0][0] -= klm->K[0] * P00_temp;

    klm->P[0][1] -= klm->K[0] * P01_temp;

    klm->P[1][0] -= klm->K[1] * P00_temp;

    klm->P[1][1] -= klm->K[1] * P01_temp;

 

   return klm->angle;

}

// Used to set angle, this should be set as the starting angle

void setAngle(struct Kalman* klm, double newAngle) { klm->angle = newAngle; }


// Return the unbiased rate

double getRate(struct Kalman* klm) { return klm->rate; }

 

/* These are used to tune the Kalman filter */

void setQangle(struct Kalman* klm, double newQ_angle) { klm->Q_angle = newQ_angle; }


/* Default value is (0.003f), raise this to follow input more closely, lower this to smooth result of kalman filter */

void setQbias(struct Kalman* klm, double newQ_bias) { klm->Q_bias = newQ_bias; }

void setRmeasure(struct Kalman* klm, double newR_measure) { klm->R_measure = newR_measure; }

double getQangle(struct Kalman* klm) { return klm->Q_angle; }

double getQbias(struct Kalman* klm) { return klm->Q_bias; }

double getRmeasure(struct Kalman* klm) { return klm->R_measure; }

#endif


mpu6050.ino

/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics. All rights reserved.

This software may be distributed and modified under the terms of the GNU

General Public License version 2 (GPL2) as published by the Free Software

Foundation and appearing in the file GPL2.TXT included in the packaging of

this file. Please note that GPL2 Section 2[b] requires that all works based

on this software must also be made publicly available under the terms of

the GPL2 ("Copyleft").

Contact information

-------------------

Kristian Lauszus, TKJ Electronics

Web : http://www.tkjelectronics.com

e-mail : kristianl@tkjelectronics.com

Updated by Joe YOON in 2017.12.02

*/


#include <Wire.h>

#include "Kalman.h"


struct kalman Kal_struct_X, Kal_struct_Y; // Create the Kalman instances


/* IMU Data */

int16_t accX, accY, accZ; // 3-axis accelerometer

int16_t tempRaw;

int16_t gyroX, gyroY, gyroZ; // 3-axis gyroscope


double accXangle, accYangle; // Angle calculate using the accelerometer

double temp; // Temperature

double gyroXangle, gyroYangle; // Rate calculate using the gyro

double compAngleX, compAngleY; // Calculate the angle using a complementary filter

double kalAngleX, kalAngleY; // Calculate the angle using a Kalman filter


uint32_t timer;

uint8_t i2cData[14]; // Buffer for I2C data


void setup() {

  Init(&Kal_struct_X); // Initialize Kalman filter for X-axis

  Init(&Kal_struct_Y); // Initialize Kalman filter for Y-axis

  Serial.begin(9600);

  Wire.begin();

  i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz

  i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling

  i2cData[2] = 0x00; // Set Gyro Full Scale Range to +/-250[deg/s]

  i2cData[3] = 0x00; // Set Accelerometer Full Scale Range to +/-2[g]

  while(i2cWrite(0x19,i2cData,4,false)); // Write to all four registers at once

  while(i2cWrite(0x6B,0x01,true)); // PLL with X axis gyroscope reference and disable sleep mode

  while(i2cRead(0x75,i2cData,1));

  if(i2cData[0] != 0x68) { // Read "WHO_AM_I" register

    Serial.print(F("Error reading sensor"));

    while(1);

  }

  

  delay(100); // Wait for sensor to stabilize

  

  /* Set kalman and gyro starting angle */

  while(i2cRead(0x3B,i2cData,6));

  accX = ((i2cData[0] << 8) | i2cData[1]);

  accY = ((i2cData[2] << 8) | i2cData[3]);

  accZ = ((i2cData[4] << 8) | i2cData[5]);


  // atan2 outputs the value of -? to ? (radians) - see http://en.wikipedia.org/wiki/Atan2

  // We then convert it to 0 to 2? and then from radians to degrees

  accYangle = atan2(-1*accX/sqrt(pow(accY,2) + pow(accZ,2)))*RAD_TO_DEG;

  accXangle = atan2(accY/sqrt(pow(accX,2) + pow(accZ,2)))*RAD_TO_DEG;

   

  setAngle(&Kal_struct_X, accXangle); // Set starting angle

  setAngle(&Kal_struct_Y, accYangle);


  gyroXangle = accXangle;

  gyroYangle = accYangle;

  compAngleX = accXangle;

  compAngleY = accYangle;

  

  timer = micros();

}


void loop() {

  /* Update all the values */

  while(i2cRead(0x3B,i2cData,14));

  accX = ((i2cData[0] << 8) | i2cData[1]);

  accY = ((i2cData[2] << 8) | i2cData[3]);

  accZ = ((i2cData[4] << 8) | i2cData[5]);

  tempRaw = ((i2cData[6] << 8) | i2cData[7]);

  gyroX = ((i2cData[8] << 8) | i2cData[9]);

  gyroY = ((i2cData[10] << 8) | i2cData[11]);

  gyroZ = ((i2cData[12] << 8) | i2cData[13]);

  

  // atan2 outputs the value of -? to ? (radians) - see http://en.wikipedia.org/wiki/Atan2

  // We then convert it to 0 to 2? and then from radians to degrees

  accYangle = atan2(-1*accX/sqrt(pow(accY,2) + pow(accZ,2)))*RAD_TO_DEG;

  accXangle = atan2(accY/sqrt(pow(accX,2) + pow(accZ,2)))*RAD_TO_DEG;

   

  double gyroXrate = (double)gyroX/131.0;

  double gyroYrate = -((double)gyroY/131.0);

  gyroXangle += gyroXrate*((double)(micros()-timer)/1000000); // Calculate gyro angle without any filter

  gyroYangle += gyroYrate*((double)(micros()-timer)/1000000);

  //gyroXangle += kalmanX.getRate()*((double)(micros()-timer)/1000000); // Calculate gyro angle using the unbiased rate

  //gyroYangle += kalmanY.getRate()*((double)(micros()-timer)/1000000);

  

  compAngleX = (0.93*(compAngleX+(gyroXrate*(double)(micros()-timer)/1000000)))+(0.07*accXangle); // Calculate the angle using a Complimentary filter

  compAngleY = (0.93*(compAngleY+(gyroYrate*(double)(micros()-timer)/1000000)))+(0.07*accYangle);

  

  kalAngleX = getAngle(&Kal_struct_X, accXanglegyroXrate(double)(micros()-timer)/1000000); // Calculate the angle using a Kalman filter

  kalAngleY = getAngle(&Kal_struct_Y, accYanglegyroYrate(double)(micros()-timer)/1000000);

  timer = micros();

  

  temp = ((double)tempRaw + 12412.0) / 340.0;

  

  /* Print Data */

  display_formatted_float(accX, 5, 0, 3, false);

  display_formatted_float(accY, 5, 0, 3, false);

  display_formatted_float(accZ, 5, 0, 3, false);

  display_formatted_float(gyroX, 5, 0, 3, false);

  display_formatted_float(gyroY, 5, 0, 3, false);

  display_formatted_float(gyroZ, 5, 0, 3, false);


  Serial.print("\t");


  display_formatted_float(accXangle, 5, 2, 3, false);

  display_formatted_float(gyroXangle, 5, 2, 3, false);

  display_formatted_float(compAngleX, 5, 2, 3, false);

  display_formatted_float(kalAngleX, 5, 2, 3, false);


  Serial.print("\t");


  display_formatted_float(accYangle, 5, 2, 3, false);

  display_formatted_float(gyroYangle, 5, 2, 3, false);

  display_formatted_float(compAngleY, 5, 2, 3, false);

  display_formatted_float(kalAngleY, 5, 2, 3, false);


  //Serial.print(temp);Serial.print("\t");


  Serial.print("\r\n");

  delay(1);

}


void display_formatted_float(double val, int characteristic, int mantissa, int blank, boolean linefeed) {

  char outString[16];

  int len;


  dtostrf(val, characteristic, mantissa, outString);

  len = strlen(outString);

  for(int i = 0; i < ((characteristic+mantissa+blank)-len); i++) Serial.print(F(" "));

  Serial.print(outString);

  if(linefeed)

    Serial.print(F("\n"));

}


Posted by Nature & Life


아두이노(Arduino) 환경에서 전형적인 MPU6050 센서 입력에 사용되는 칼만 필터(Kalman filter)의 예제입니다.

http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it/#comment-57783


또한 이 칼만 필터는 가속도계(accelerometer) 혹은 지자계(magnetometer)와 그리고 자이로(gyroscope)로부터 각도(angle), 각속도(rate) 그리고 bias를 계산하는데 사용될 수 있습니다(C++ version).

https://github.com/TKJElectronics/KalmanFilter


다음은 C version입니다.

http://www.cnblogs.com/zjutlitao/p/3915786.html



Kalman.h

/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics-> All rights reserved->


 This software may be distributed and modified under the terms of the GNU

 General Public License version 2 (GPL2) as published by the Free Software

 Foundation and appearing in the file GPL2->TXT included in the packaging of

 this file-> Please note that GPL2 Section 2[b] requires that all works based

 on this software must also be made publicly available under the terms of

 the GPL2 ("Copyleft")->


 Contact information

 -------------------


 Kristian Lauszus, TKJ Electronics

 Web      :  http://www->tkjelectronics->com

 e-mail   :  kristianl@tkjelectronics->com

 */


#ifndef _Kalman_h

#define _Kalman_h

struct Kalman {

    /* Kalman filter variables */

    double Q_angle; // Process noise variance for the accelerometer

    double Q_bias; // Process noise variance for the gyro bias

    double R_measure; // Measurement noise variance - this is actually the variance of the measurement noise


    double angle; // The angle calculated by the Kalman filter - part of the 2x1 state vector

    double bias; // The gyro bias calculated by the Kalman filter - part of the 2x1 state vector

    double rate; // Unbiased rate calculated from the rate and the calculated bias - you have to call getAngle to update the rate


    double P[2][2]; // Error covariance matrix - This is a 2x2 matrix

    double K[2]; // Kalman gain - This is a 2x1 vector

    double y; // Angle difference

    double S; // Estimate error

};


void Init(struct Kalman* klm){

    /* We will set the variables like so, these can also be tuned by the user */

    klm->Q_angle = 0.001;

    klm->Q_bias = 0.003;

    klm->R_measure = 0.03;


    klm->angle = 0; // Reset the angle

    klm->bias = 0; // Reset bias

   klm->P[0][0] = 0; // Since we assume that the bias is 0 and we know the starting angle (use setAngle), the error covariance matrix is set like so - see: http://en->wikipedia->org/wiki/Kalman_filter#Example_application->2C_technical

    klm->P[0][1] = 0;

    klm->P[1][0] = 0;

    klm->P[1][1] = 0;

}


// The angle should be in degrees and the rate should be in degrees per second and the delta time in seconds

double getAngle(struct Kalman * klm, double newAngle, double newRate, double dt) {

    // KasBot V2  -  Kalman filter module - http://www->x-firm->com/?page_id=145

    // Modified by Kristian Lauszus

   // See my blog post for more information: http://blog->tkjelectronics->dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it


    float P00_temp;

    float P01_temp;


    // Discrete Kalman filter time update equations - Time Update ("Predict")

    // Update xhat - Project the state ahead

    /* Step 1 */

    klm->rate = newRate - klm->bias;

    klm->angle += dt * klm->rate;

    

    // Update estimation error covariance - Project the error covariance ahead

    /* Step 2 */

    klm->P[0][0] += dt * (dt*klm->P[1][1] - klm->P[0][1] - klm->P[1][0] + klm->Q_angle);

    klm->P[0][1] -= dt * klm->P[1][1];

    klm->P[1][0] -= dt * klm->P[1][1];

    klm->P[1][1] += klm->Q_bias * dt;

    

    // Discrete Kalman filter measurement update equations - Measurement Update ("Correct")

    // Calculate Kalman gain - Compute the Kalman gain

    /* Step 4 */

    klm->S = klm->P[0][0] + klm->R_measure;


    /* Step 5 */

    klm->K[0] = klm->P[0][0] / klm->S;

    klm->K[1] = klm->P[1][0] / klm->S;

    

    // Calculate angle and bias - Update estimate with measurement zk (newAngle)

    /* Step 3 */

    klm->y = newAngle - klm->angle;


    /* Step 6 */

    klm->angle += klm->K[0] * klm->y;

    klm->bias += klm->K[1] * klm->y;

    

    // Calculate estimation error covariance - Update the error covariance

    /* Step 7 */

   P00_temp = klm->P[0][0];

   P01_temp = klm->P[0][1];


    klm->P[0][0] -= klm->K[0] * P00_temp;

    klm->P[0][1] -= klm->K[0] * P01_temp;

    klm->P[1][0] -= klm->K[1] * P00_temp;

    klm->P[1][1] -= klm->K[1] * P01_temp;


    return klm->angle;

}


 // Used to set angle, this should be set as the starting angle

void setAngle(struct Kalman* klm, double newAngle) { klm->angle = newAngle; }

// Return the unbiased rate

double getRate(struct Kalman* klm) { return klm->rate; }
 

/* These are used to tune the Kalman filter */

void setQangle(struct Kalman* klm, double newQ_angle) { klm->Q_angle = newQ_angle; }


/* Default value is (0.003f), raise this to follow input more closely, lower this to smooth result of kalman filter */

void setQbias(struct Kalman* klm, double newQ_bias) { klm->Q_bias = newQ_bias; }


void setRmeasure(struct Kalman* klm, double newR_measure) { klm->R_measure = newR_measure; }

double getQangle(struct Kalman* klm) { return klm->Q_angle; }

double getQbias(struct Kalman* klm) { return klm->Q_bias; }

double getRmeasure(struct Kalman* klm) { return klm->R_measure; }


#endif


Kalman.h



Posted by Nature & Life


아두이노(Arduino) 환경에서 전형적인 MPU6050 센서 입력에 사용되는 칼만 필터(Kalman filter)의 예제입니다.

http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it/#comment-57783


Step 1)



위 식에서 각도(angle)의 추정치 는 이전 상태의 추정치 에 bias 되지 않은 각속도(rate)에 곱하기 미소 시간 를 더한 것과 같습니다. bias 되지 않은 각속도(rate)에 곱하기 미소 시간 은 결국 드리프트 되지 않은 각도의 증분이 됩니다. 또한 좌측항은 아직 보정되지 않았음을 기억합니다.

게다가 우리는 bias를 직접 측정할 수 없기 때문에 bias의 추정치는 이전 것을 사용합니다.


이는 다음과 같이 C 언어로 쓸 수 있습니다.

rate = newRate - bias;

angle += dt * rate;


Step 2)



위 식은 다음과 같이 C 언어로 쓸 수 있습니다.

P[0][0] += dt * (dt*P[1][1] - P[0][1] - P[1][0] + Q_angle);

P[0][1] -= dt * P[1][1];

P[1][0] -= dt * P[1][1];

P[1][1] += Q_gyroBias * dt;


Step 3)



참고로 우측에 현재 상태변수는 보정되지 않았기 때문에 angle 변수를 그대로 사용합니다.

위 식은 다음과 같이 C 언어로 쓸 수 있습니다.


y = newAngle - angle;


Step 4)




위 식은 다음과 같이 C 언어로 쓸 수 있습니다.

S = P[0][0] + R_measure;


Step 5)



다른 경우에 S는 행렬이 될 수 있습니다. 그 경우에는 여러분은 간단히 S로 P를 나눌 수 없으며 역행렬을 구해서 곱해야 합니다.

위 식은 다음과 같이 C 언어로 쓸 수 있습니다.

K[0] = P[0][0] / S;

K[1] = P[1][0] / S;


Step 6)



위 식은 다음과 같이 C 언어로 쓸 수 있습니다.

angle += K[0] * y;

bias += K[1] * y;


Step 7)



상태 추정 오차가 감소되었기 때문에 오차 공분산 행렬을 다시 감소시킴을 기억하세요.

C 코드는 다음과 같습니다.

float P00_temp = P[0][0];

float P01_temp = P[0][1];


P[0][0] -= K[0] * P00_temp;

P[0][1] -= K[0] * P01_temp;

P[1][0] -= K[1] * P00_temp;

P[1][1] -= K[1] * P01_temp;


참고로 대부분의 IMU에 대해서 다음의 변수들이 완벽하게 동작합니다.

float Q_angle = 0.001;

float Q_gyroBias = 0.003;

float R_measure = 0.03;


초기치로서 각도를 설정하는 것을 기억하세요 왜냐하면 필터가 안정화되는데 시간이 걸리기 때문입니다. 반대로 칼만 필터가 안정화되기 전까지는 상태의 추정치를 믿을 수 없습니다.



Posted by Nature & Life


아두이노(Arduino) 환경에서 전형적인 MPU6050 센서 입력에 사용되는 칼만 필터(Kalman filter)의 예제입니다.

http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it/#comment-57783


한편 의 관측 혹은 측정을 위한 식은 다음과 같이 주어집니다.



위 식에서와 같이 는 현재 상태 가  행렬과 곱하여지고 측정 잡음 와 합하여 집니다.


는 관측 모델로 불려지며 실제 상태 공간을 관측된 상태의 공간으로 매핑하기 위해서 사용되어집니다. 실제 상태는 관측될 수 없는데 이는 측정이 단지 가속도계로부터의 측정에 불과하기 때문입니다.


는 다음과 같습니다.



측정 잡음은 게다가 '0'의 평균과 다음과 같이 의 공분산을 가져야 합니다. 



그러나 은 행렬이 아니기 때문에 측정 잡음은 단지 측정값의 분산과 같습니다. 왜냐하면 같은 변수의 공분산은 분산과 같기 때문입니다. 그러므로 우리는 R을 다음과 같이 쓸 수 있습니다.



우리는 측정 잡음은 동일하고 시간 k에 의존하지 않는다고 가정합니다.



만일 여러분이 측정 잡음 분산 을 너무 높게 설정하면 필터는 너무 늦게 응답하는데 이는 새로운 측정값을 덜 믿기 때문이며, 반면에 너무 낮게 설정한다면 값은 오버슈트(overshoot)가 발생하고 잡음이 많은데 이는 우리가 가속도계 측정값을 너무 많이 믿기 때문임을 참고하세요.


그래서 여러분은 프로세스 잡음 분산 와  그리고 측정 잡음 의 측정 분산을 찾아야만 합니다.


Time Update("Predict")


처음 2개의 방정식에서 시간 k에서 우리는 현재 상태와 오차 공분산 행렬을 예측할 것입니다. 우선 필터는 모든 이전 상태들과 자이로 측정으로부터 근거 된 현재 상태를 추정할 것입니다.



위 식의 우측 두번째 항을 제어 입력이라고 부르는 이유는 우리가 현재 시간 k에서 상태를 추정하기 위해 이를 추가적인 입력으로 사용했기 때문입니다. 다음은 우리가 이전 오차 공부산 행렬 에 근거하여 다음에 정의된 것처럼 오차 공분산 행렬 을 추정할 것입니다.



이 행렬은 추정된 상태의 현재 값을 얼마나 많이 신뢰하는가를 추정하기 위해서 사용되어집니다. 작을수록 우리는 좀더 현재 추정된 상태를 신뢰하게 됩니다. 위 방정식의 원리는 실제로 이해하기에 매우 쉽습니다. 이는 우리가 상태의 추정을 마지막 갱신한 후에 오차 공분산은 증가하는 것이 매우 분명하기 때문입니다. 그러므로 우리는 오차 공분산 행렬을 상태 전이 모델 과 그 행렬의 전치행렬 을 곱하고 시간 k에서 현재 프로세스 잡음 를 더합니다.


오차 공분산 행렬 는 2x2 행렬입니다.



Measurement Update("Correct")


우리가 계산해야 할 첫번째 일은 측정치 와 보정되지 않은 이전 상태로부터 예측된 상태 변수 사이에 차를 계산하는 것입니다.



여기서 관측 모델 는 이전 상태 추정치를 관측된 공간과 매핑하게 위해서 사용되어집니다. 관측 공간이라 함은 가속도계로부터의 측정치로 그러므로 위 식은 행렬이 아닙니다.



다음에 할 것은 를 계산하는 것입니다.



이전 오차 공분산 행렬 와 측정 공분산 행렬 에 근거한 측정을 얼마나 믿을 수 있는지 예측하는 것입니다. 관측 모델 는 이전 오차 공분산 행렬 를 관측 공간으로 매핑하기 위해서 사용되어집니다.

큰 측정 잡음은 값을 크게 합니다. 이는 입력되는 측정값을 그렇게 많이 신뢰할 수 없음을 의미합니다. 이 경우 는 행렬이 아니고 다음과 같이 쓸 수 있습니다.



다음 단계는 칼만 이득(Kalman gain)을 계산하는 것입니다. 칼만 게인은 측정치 와 이전 상태 변수와의 차이를 얼마나 신뢰할 수 있는지를 나타내는데 사용되어집니다.



만일 우리가 그 차이를 도저히 신뢰하지 못한다면, 차이에 대한 공분산 는 높게 될 것입니다. 그리고 만일 우리가 추정된 상태를 신뢰한다면 오차 공분산 행렬 는 작게 될 것이고 칼만 이득도 작아질 것입니다. 반대로 차이를 신뢰하지만 현재 상태의 추정치를 믿지 못한다면 반대가 될 것입니다.

여기서 관측 모델 H의 전치행렬이 오차 공분산 행렬 P를 관측 공간으로 매핑하는데 사용되어짐을 알 수 있습니다. 그다음 우리는 공분산 S로 나누어 오차 공분산 행렬을 비교합니다.

이는 관측 모델 H를 상태 오차 공분산을 빼기 위해서 그리고 S의 현재 추정치와 비교하기 때문에 의미가 있다는 것입니다. 만일 여러분이 초기에 상태를 모른다면 오차 공분산 행렬을 다음과 같이 설정할 수 있다.



여기서 은 큰 숫자를 대표하며 초기에 상태가 알려진다면 오차 공분산 행렬을 다음과 같습니다.



칼만 이득은 2x1 행렬입니다.



우리는 추정된 현재 상태를 보상합니다.




이는 yk와 칼만 이득을 곱하여 보상되지 않은 현재 상태에 더하는 것입니다.


는 측정된 와 보상되지 않은 추정된 현재 상태의 차이로 양수나 음수가 될 수 있습니다.


가장 간단화된 방정식은 보상되지 않은 현재 추정 상태 를 가속도계 측정값으로 간단하게 바로잡는 것으로 이해할 수 있습니다.

여기서 는 이전 상태와 자이로 측정값으로부터 계산되어진 것입니다.


마지막으로 우리는 보상되지 않은 오차 공분산 행렬을 갱신하는 것입니다.



여기서 I는 항등행렬입니다.



필터가 하는 일은 기본적으로 얼마나 많이 우리가 추정치를 바로잡았는가에 근거하여 오차 공분산 행렬을 스스로 바로잡는 것입니다. 이것은 우리가 이전의 오차 공분산 과 공분산 에 근거된 상태를 바로잡았기 때문에 의미가 있다는 것입니다. 



Posted by Nature & Life


아두이노(Arduino) 환경에서 전형적인 MPU6050 센서 입력에 사용되는 칼만 필터(Kalman filter)의 예제입니다.

http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it/#comment-57783


MPU6050은 가속도와 자이로센서가 1개의 센서에 모두 포함하고 있는 6DOF(Degrees of Freedom) 센서로, I2C(Inter Integrated Circuit) 통신 프로토콜을 통해서 데이터를 추출 할 수 있습니다. 위 모듈은 MPU6050을 포함하며 사용하기 쉽게 만든 모듈입니다. 


MPU6050의 간단한 스펙입니다.


가속도계(accelerometer)는 움직임이 있을때 일반적으로 많은 잡음(noise)을 포함합니다. 자이로(gyroscope)의 문제는 시간이 누적되면 DC 성분(혹은 이를 bias라 표현)이 누적된다는 것입니다. 결론적으로 자이로는 짧은 시간에 믿을 수 있지만, 반면 가속도계는 긴 시간에 더 신뢰성이 있다는 것입니다.



그렇기 때문에 상보 필터(complimentary filter, 혹은 보상 필터)의 적용이 쉽습니다. 기본적으로 가속도계에 대해서 디지털 low pass filter(LPF), 자이로에 대해서는 디지털 high pass filter(HPF)를 적용하는 것입니다. 그러나 칼만 필터(Kalman filter) 만큼 정확하지는 않다는 것입니다. 근래에 아두이노를 이용하여 처리가 가능하지만 칼만 필터는 아두이노가 주로 제공하는 MCU에서는 무겁다는 단점이 있습니다.


k 시간에 시스템의 상태는 다음과 같습니다.



여기서 는 상태 행렬로 다음과 같습니다.



는 각도 의 시간에 따라 누적된 양으로 bias라고 하며 자이로에서 측정된 값에서 이 bias를 빼는 것에 의하여 현재의 각도를 얻을 수 있습니다.


그러므로 이전 상태 로부터의 전이 행렬 는 다음과 같이 쓸 수 있습니다.



는 제어 입력으로 k 시점에서 자이로의 측정값으로 [˚/s]의 단위를 가지며 이 로 상태방정식을 다음과 같이 다시 쓸 수 있습니다.



여기서  제어 입력 행렬로 다음과 같이 나타낼 수 있습니다.



는 프로세스 잡음으로, 주어진 k 시간에서 '0'의 평균과 의 공분산(covariance)을 갖는 가우시안 분포(Gaussian distribution)(혹은 정규분포)를 갖습니다.



는 프로세스 잡음 공분산 행렬이고, 이 경우에 가속도계와 bias의 상태 추정치 공분산 행렬이 됩니다. 우리는 bias와 가속도계의 추정이 서로 독립적임을 가정합니다.



위 식에서 처럼  공분산 행렬은 현재 시간 k에 의존합니다. 따라서 가속도계 분산 와 bias의 분산 은 미소 시간 에 곱하여집니다.

프로세스 잡음은 시간이 길어질수록 커지게 됨을 의미합니다. 이는 상태의 마지막 업데이트이기 때문입니다. 예를 들어 자이로는 드리프트(drift) 할 수 있습니다. 우리는 칼만 필터의 동작을 위해서 이들 상수들을 알아야 합니다. 만일 여러분이 큰 값을 설정한다면 상태 추정에 잡음이 커진다는 것을 기억해야 합니다. 예를 들어, 추정 각도가 드리프트 하기 시작하면 여러분은 의 값을 증가시켜야 합니다. 반면에 추정이 여러분이 생각하는 것보다 너무 많이 느려진다면 반응이 빠르도록 의 값을 감소시켜 볼 수 있습니다.



Posted by Nature & Life


3축 가속도와 3축 자이로 센서를 조합한 후 각각의 센서 출력을 내보내는 장치를 관성측정장치(IMU; Inertial Measurement Unit)이라고 부릅니다. 스마트폰에도 탑재되어 있을 정도로 그 용도가 근래에 흔하며, 비행기의 항법 장치에 필수적인 요소입니다. 기체의 자세 제어에 요구되는 롤(Roll), 피치(Pitch), 요(Yaw)의 기울어진 각도를 알기 위함으로, 롤은 좌우로 기울어짐, 피치는 앞뒤로 기울어짐, 요는 z축 방향으로 기울어짐(회전각)을 의미합니다. 여기서 롤과 피치는 중력방향을 기준으로 얼마나 기울어져 있는지를 나타냅니다.



이처럼 롤과 피치 그리고 요와 같은 자세 측정을 위해서는 필요한 장치가 자이로와 가속도 센서입니다.


MEMS 기반의 3축 가속도 센서(Accelerometer)는 x축, y축, z축 방향의 가속도를 측정할 수 있으며 단위는 [g]입니다. 가속도 센서는 정지한 상태에서 중력 가속도를 감지하기 때문에 z축 방향으로 -g 만큼의 값을 출력합니다. 센서의 초기 출력은 모두 '0'이라고 가정하고 센서를 y축 기준으로 45도 기울여 보면, 기울어진 상태에서 z축 방향과 x축 방향으로 동일한 값의 가속도가 측정되며 중력방향으로 g가 측정되어야 하므로 0.707g 만큼 z축과 x축 방향으로 값이 출력됩니다. 결과적으로는 z축과 x축 값의 비율을 arctan으로 계산하여 기울어진 값을 구할 수 있습니다.



그러나 정지 상태가 아닌 움직이는 가속 상태의 경우, 또다른 힘의 영향으로 중력 방향이 변한 것처럼 올바른 값을 얻을 수가 없습니다. 즉, 정지하지 않은 움직임 상태에서는 가속도 센서만으로 기울기 값을 측정할 수는 없습니다. 뿐만 아니라 z축상에 회전각인 요는 중력 방향(중력가속도)이 전혀 변하지 않으므로 측정이 불가능합니다.


3축 자이로 센서(Gyroscope)는 가속도를 측정하는 가속도 센서와 달리 각속도를 측정하므로 단위는 [degree/sec]입니다. 자이로는 각속도를 재는 장치이기에 이를 이용해서 각도를 알려면 전체 시간에 대하여 적분을 하여 얻게 됩니다. 그러나 센서에서 측정되는 각속도는 노이즈나 어떠한 이유에 의해 측정값에 에러가 계속 생기는데, 이 오차가 적분시에는 누적이 되어서 최종 값이 드리프트 되는 현상이 발생합니다. 게다가 시간이 지날수록 이 오차는 커져 각도가 변하게 된다는 것입이다.


결과적으로 정지상태의 긴 시간의 관점에서 보면 가속도 센서에 의해 계산된 각도는 올바른 값을 보여주지만, 자이로 센서에서는 시간이 지날 수록 틀린 값을 내보내게 됩니다. 반대로 움직이는 짧은 시간의 관점에서 보면 자이로 센서는 올바른 값을 보여주지만 가속도 센서는 다른 값을 내보내게 됩니다.


그러므로 가속도 센서와 자이로 센서를 모두 사용해서 각각의 단점을 보상할 수 있는 알고리즘을 적용하여 롤 또는 피치 값을 계산하여야 한다는 것입니다. 많이 사용하는 보상 방법 및 필터링으로는 칼만 필터(Kalman filter)의 적용입니다.


요의 회전축은 z축방향, 즉 중력방향과 같으므로 가속도 센서가 아닌 자이로 센서의 z축 값을 측정해서 이 값을 이용해 요값을 계산하여야 하고 드리프트되는 오차를 보상하는 다른 센서를 추가적으로 사용하는데, 이것이 지자기 센서(magnetometer or compass)입니다. 자이로는 온도가 변하면 그 값이 같이 변하는 특성이 있어 정교한 측정을 위해서는 온도 센서도 함께 사용해서 오차를 보상하기도 합니다. 이를 모두 고려하면 3축 가속도 센서, 3축 자이로 센서, 3축 지자기 센서를 내장한 IMU 센서를 9축 센서라 부르는 이유에서 입니다.



위 그림의 칩은 Invensense사의 9DOF IMU인 MPU-9150입니다. 이는 3축 자이로 센서와 3축 가속도 센서인 MPU-6050과 3축 지자기 센서(digital compass)를 One Pakage(SiP)하였으며, I2C(TWI)를 지원합니다. 여기서 DOF는 'Degree Of Freedom'의 약자로 '축(Axis)'을 의미합니다.



'Flight Controller 이해 > 센서' 카테고리의 다른 글

MPU6050 센서  (1) 2017.12.01
드론에 요구되는 각종 센서들  (0) 2017.02.26
Posted by Nature & Life