[아두이노] MPU-6050 + Stepper Motor 제어

IOT/아두이노|2019. 5. 15. 09:00

[아두이노] MPU-6050 + Stepper Motor 제어



MPU-6050 가속도/자이로 센서를 그냥 끝내기가 아쉬워서 간단히 실제 뭔가를 움직이게 하는 실험을 하면 좋을 것 같아서 오늘 Post 주제로 결정했습니다. 복잡한 제어는 아니고 간단히 원리만 이해하는 차원으로 하나의 패턴 동작만 수행하도록 제어하는 코딩을 실험 할 예정입니다. 설정은 MPU-6050 모듈의 기울기 각도 중 하나 X축의 각도값에 따라 Stepper Motor를 회전 시키는 조건입니다. 이 회전은 각도의 변화 크기에 따라 회전시키는 각의 결정하는 명령입니다. 즉, X축의 기울기 각도가 커지면 커질수로 회전의 각도 변화는 크게 회전시키고 X축의 기울기 각도가 작아지면 작아질수로 회전의 각도 변화는 작게 회전 시킵니다. 여기서, X축의 기울기가 반대로 기울려졌을 때는 역방향으로 회전 시키게 됩니다.

복잡한 설정을 하면 코딩량이 늘어나고 오히려 가독성이 떨어지고 의미 전달이 되지 않기 때문에 간단히 MPU-6050 모듈의 기울기에 따라서 Stepper Motor의 회전각을 제어하는 실험입니다. 이 원리를 꼭 기억해 두셨다가 나중에 이 원리를 회전의 속도로 변형해서 실험 해보세요. 그리고 드론에서의 x,y,z 회전각을 통해서 4개의 Motor의 회전 속도 제어를 통한 수평 조절에 대한 구상을 머리속에서 상상 코딩을 해보세요.

그러면, MPU-6050 모듈로 Stepper Motor를 제어하는 실험을 해보겠습니다.


1. MPU-6050 + Stepper Motor 회로도




2. 코딩



1) 기본 소스


[기본베이스 소스] - GetAngle (tockn님 소스 기반)

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);
void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  //mpu6050.calcGyroOffsets(true);
   mpu6050.setGyroOffsets(0.75, 0.05, 0.05);
}
void loop() {
  mpu6050.update();
  int val=mpu6050.getAngleX();
  Serial.println(angle); 
}

mpu6050.setGyroOffsets(0.75, 0.05, 0.05);

여기서, 위 인자값은 mpu6050.calcGyroOffsets(true) 함수로 사전에 계산된 값입니다.

지난시간 복습차원으로 다시 설명 드리면, 처음에는 setGyroOffsets()함수는 주석 처리하고 calcGyroOffsets()함수의 주석을 풀고 해당 찍히는 값을 출력해 주세요. 거기서 측정 된 값은 GyroOffsets값을 메모장에 다 적어놓고 setGyroOffsets() 함수의 인자값으로 넣으면 사전에 calcGyroOffsets()함수에서의 계산 시간을 줄일 수 있습니다.

아래 그림은 MPU6050_tockn 라이브러리의 GetAngle를 돌렸던 결과 이미지 입니다. 지난시간의 이미지인데 약간 위 코딩의 인자값하고 다른데, 처음 측정된 위치가 다르기 때문에 해당 값은 다를 수 밖에 없습니다.


지금 다시 측정을 하면 위치가 좀 달라지면 또 다른 값으로 바뀌겠지요. 원래는 계산하면 좋지만 빠르게 실험을 하기 위해서 한번 계산하고 그 값을 기준으로 세팅해놓고 실험하시면 빠르게 테스트 할 수 있습니다. 나중에 정교한 제어를 한다면 계산이 필요하겠지만 간단히, 원리 테스트에서는 처음 한번만 calcGyroOffsets()함수로 x,y,z GyroOffset 값을 구해 놓고 setGyroOffsets()함수에 인자로 넣고 두번째 부터는 빠르게 테스트 하는게 더 좋겠죠.

2) MPU-6050의 X축 회전각에 따른 Stepper Motor 회전


변화각 구해서 그 변화각 만큼 회전을 시켜 보자.

  int val=mpu6050.getAngleX();
  angle = angle + val-previousAngle;
  previousAngle=val;  
  stepper.step(angle);

val에서 우선 MPU-6050 모듈에서 X축의 회전각을 구하게 됩니다. 현재의 회전각은 val에 저장됩니다. 여기서, previousAngle 변수는 이전 getAngleX() 각을 저장되어 있습니다. 매 loop()함수가 돌 때마다 getAngleX() 각을 previousAngle 변수에 저장합니다.

그러면,

현재 X회전각 - 이전X회전각 = X회전각의 변화값

이렇게 얼마만큼의 변화가 발생했는지 그 차이 값을 구할 수 있게 됩니다.

이때,

angle = angle + val-previousAngle;

이렇게 하면 angle은 X회전각의 변화값을 누적하는 변수입니다. + 방향으로 변화각이 계속 커질 때 angle의 값은 그 변화각만큼 계속 증가하게 되고 만약에 - 방향으로 변화각이 계속 변할때 그 변화각만큼 angle의 값은 그 변화각 만큼 계속 감소하게 됩니다.

stepper.step(angle);

그리고, angle을 Stepper Motor의 회전각으로 설정하면 그 방향 각도만큼 정방향 회전이나 역방향 회전을 하게 됩니다.

참고로, 위 stepper.step(angle)에서 angle은 스템수입니다. 즉 180값이라고 하면 180도 회전하는게 아닙니다. 180 스템수만큼 정방향으로 회전 시키는 것일 뿐입니다. 180도 만큼의 회전을 주고 싶다면 1024스템수로 회전 시켜야 합니다.

stepper.step(map(angle,0,360,0,2048));

이렇게 되어야 정상적인 각도 회전이 되겠죠.

3) Stepper Motor


#include <Stepper.h>

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);

void setup() {
 stepper.setSpeed(12); 
}
void loop() {
 stepper.step(angle);
}

헤더파일, Stepper 객체변수 선언하고 setup()함수안에서 stepper.setSpeed() 로 스피드를 설정합니다. 그리고 나서 실제적으로 Stepper Motor를 stepper.step() 함수로 실제 회전을 시키게 됩니다. 이 코딩 부분은 위에 MPU-6050의 코딩과 합쳐야 겠죠.

4) 코딩 합치기


#include <Stepper.h>
#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);
int angle = 0;
int previousAngle=0;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  //mpu6050.calcGyroOffsets(true);
  mpu6050.setGyroOffsets(0.75, 0.05, 0.05);
  stepper.setSpeed(12); 
}
void loop() {
  mpu6050.update();
 
  int val=mpu6050.getAngleX();
  angle = angle + val-previousAngle;
  previousAngle=val;  
     
  stepper.step(angle);
  Serial.println(angle); 
  delay(50);
}

삽입해야 할 위치에 코딩을 배치하면 됩니다. 완성된 소스는 좀 복잡해 보일 수 있지만 각 부품에 대해 의미만 제대로 이해하고 있다면 해당 위치에 삽입하는데 어려움이 없을 거라 생각 됩니다.

3. 실험 결과


아래 움짤 이미지는 회전각의 변화를 시리얼모니터로 출력한 결과물입니다. MPU-6050의 X축 회전각의 변화가 실제 Stepper Motor에 회전각의 값이 어떻게 변화되는지 잘 살펴보세요.



다음은 실제 MPU-6050모듈을 움직일 때 X축 회전각에 의해 Stepper Motor 회전되는 모습입니다.

마무리


간단히 MPU-6050 모듈에서 측정된 X축 회전각의 값을 통해서 Stepper Motor를 회전 시켰습니다. 여기서, 중요한 것은 MPU-6050의 값이 다른 부품의 움직임을 만들었다는 것이고 그것을 코딩으로 표현했다는데 의미가 있습니다.

오늘 실험은 Stepper Motor를 움직이게하는 첫발을 내딛는 코딩입니다. 여기서 부터 여러분들이 코딩에 살을 붙여가면서 상상력을 동원해서 뭔가를 하나씩 추가해보셨으면 합니다.

첫발은 내딛는데까지는 안내해드렸고 두번째 발은 어려분들이 내딛을 차례입니다.
한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] MPU-6050 센서 + processing 연결 제어

IOT/아두이노|2019. 5. 13. 09:00

[아두이노] MPU-6050 센서 + processing 연결 제어



어제 MPU-6050 가속도/자이로센서에 대해 간단히 테스트를 해 보았습니다. 어제는 MPU-6050 라이브러리 없이 I2C 통신으로 센서의 값을 가져왔는데 오늘은 MPU-6050 라이브러리를 가지고 쉽게 제어하는 방법을 살펴보도록 하겠습니다. 직접 식을 만들고 그 식을 통해서 제어하면 좋겠죠. 하지만 입문자나 초보분들은 수학적으로 알아야 할 것이 너무 많습니다. 입문자의 경우는 수학적으로 접근한다면 실제 MPU-6050를 다루기 전에 지쳐버리겠죠. 처음에는 흥미를 가지고 출발해야 좀 더 관심을 가지고 공부하게 됩니다. 수학적인 부분을 모르더라도 누군가 만들어 놓은 라이브러리를 활용하여 MPU-6050 모듈을 사용할 수 있으니 부담없이 처음에 접근하기 바랍니다. 가격이 저렴한 모듈이기에 정교한 제어용으로는 다소 무리가 있습니다. 단지 입문자용으로 부담없이 사용할 수 있는 모듈이라고 생각하시면 되겠습니다.

오늘은 실험에 사용할 라이브러는 인터넷에서 찾아보면 두개의 라이브러리가 있는데 아두이노 IDE 라이브러리관리 창에서 검색한 라이브러리를 설치하여 사용하겠습니다. 그리고, 이 라이브러리 예제를 통해 얻은 값을 테스트 한 후에 이 예제를 통해서 processing으로 만든 box 도형을 MPU-6050 모듈로 움직임을 제어하는 실험을 하겠습니다.


1. MPU-6050 가속도/자이로 센서


1) MPU-6050 가속도/자이로 센서 회로도


  • 준비물 : MPU-6050 모듈, 아두이노우노
  • 내용 : I2C 통신을 위해 A4(SDA), A5(SCL)핀에 연결하시오

지난 시간에 회로도와 동일합니다.


2) 라이브러리 설치


아두이노 IDE 라이브러리관리창에서 "MPU-6050"을 검색하면 아래 그림처럼 tockn님이 만든 라이브러리가 하나만 검색됩니다. 참고로 아래에 MPU6050으로 한개 더 있는데 이건 이미 설치된 라이브러인데 외부 구글검색으로 찾아서 다운받은 후 라이브러리를 추가했기 때문에 2개의 라이브러리가 표시 된 모습입니다.


직접 검색해서 다른 라이브러리를 다운 받으실려면 "mpu-6050 github"라고 구글검색하시면 해당 링크가 나옵니다.

https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

3) 코딩



예제에 가시면 두개의 GetAllData, GetAngle 예제가 있습니다. 둘 다 한번씩 돌려보시기 바랍니다.


여기서, GetAllData 예제은 1초 단위로 전체 데이터를 출력하는 예제이고, GetAngle 예제는 회전각 X, Y, Z 값만 출력하는 예제입니다.

실험은 GetAngle 예제를 사용 하겠습니다. 이걸로 아래 Processing으로 표현한 box 회전에 사용하기 위해서 입니다.

[GetAngle 소스]-tockn님 소스

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
}

void loop() {
  mpu6050.update();
  Serial.print("angleX : ");
  Serial.print(mpu6050.getAngleX());
  Serial.print("\tangleY : ");
  Serial.print(mpu6050.getAngleY());
  Serial.print("\tangleZ : ");
  Serial.println(mpu6050.getAngleZ());
}

소스를 살펴보면,

  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);

세가지 Wire, mpu6050 시작선언과 초기 작업으로 자이로 오프셋값을 계산합니다. 교정작업으로 자이로 오프셋 평균값을 계산하는데 한번 해당 함수들의 로직을 살펴봐 주세요. 이게 나중에 실험할 때 시간을 많이 잡아먹는 부분이더군요. 평균값을 구하기 위해서 자이로 x,y,z 값을 5000번 읽어고 그 값을 5000으로 나눠서 평균값을 계산하니깐 계산시간이 좀 걸려서 실험 시 대기시간이 길어지더군요.

참고로 값을 알고 있다면

mpu6050.setGyroOffsets(1.45, 1.23, -1.32);

이런식으로 값을 지정 할 수 있습니다.

위, 3줄의 명령은 시작 초기화 함수입니다.

loop()에서는 mpu6050.update() 함수로 이전 라이브러리 없이 읽기 요청과 읽는 함수들의 로직이 이 함수안에 다 집어 넣었더군요. 그리고 그 안에서 상보필터로 회전각을 구하는 부분까지 다 포함되어 있는데 관심있는 분들은 링크 걸어놓은 곳에 "MPU6050_tockn.cpp" 가셔서 로직을 살펴보시면 됩니다.

  • 상보 필터 : filtered_angle = (0.02 * accel) + (0.98 * gyro) (tockn님 계산식)

아무튼 총 4개의 함수를 사용하면 입문자분들은 따로 계산식을 걱정할 필요 없이 회전각 mpu6050.getAngleX(), mpu6050.getAngleY(), mpu6050.getAngleZ() 함수를 통해서 값을 얻을 수 있게 됩니다.

라이브러리를 이용하니깐 그렇게 까지 복잡하지 않죠.

[결과]
다음과 같은 결과가 출력됩니다.


2. MPU-6050 + processing 실험


1) MPU-6050 회전각을 시리얼통신을 통해 String으로 전송하기


[아두이노 실험 소스]

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(19200);
  Wire.begin();
  mpu6050.begin();
  //mpu6050.calcGyroOffsets(true);
    mpu6050.setGyroOffsets(0.75, 0.05, 0.05);
}

void loop() {
  mpu6050.update();  
  Serial.print(mpu6050.getAngleX());
  Serial.print(',');
  Serial.print(mpu6050.getAngleY());
  Serial.print(',');
  Serial.println(mpu6050.getAngleZ());
  delay(50);
}

위 소스를 보시면 getAngle X,Y,Z 사이에 콤마(,)을 붙여서 한줄로 3개의 데이터가 시리얼 통신으로 전송됩니다. delay(50)을 정도를 설정했는데 안써도 상관 없습니다.

2) getAngle 수정(선택 사항)


이상태로 실험하셔도 되지만 좀 더 빠르게 실험하기 위해서 초기값을 주도록 하겠습니다. calcGyroOffsets(true)함수로 초기값을 계산하는게 아니라 직접 처음에 초기값을 주어서 빠르게 동작하도록 변경하겠습니다.

처음 한번은 getAnglem 예제를 실행하여 아래와 같은 X,Y,Z값을 얻게 되면 그 값을 메모장에 적어놓으세요


처음 getAngle 예제를 돌리면 (X,Y,Z) 가 (0.67, 0.10, 0.10)으로 계산 되었네요. 5천번의 MPU-6050 모듈에 측정된 값을 계산하기 때문에 시간이 걸립니다. 시작 위치의 초기값을 먼저 잡아놓으면 다음 계산이 필요 없기 때문에 빠르게 회전각을 얻을 수 있게 됩니다.

변경은 다음과 같은 calcGyroOffsets()을 주석처리하고 setGyroOffsets()함수로 선언해주면 됩니다. 아래의 X,Y,Z 값은 processing과 연결하여 실험 할 당시의 X, Y, Z의 값입니다. 즉, 여러분들이 실험하는 장소와 위치에서 처음 측정된 값으로 초기값으로 할 경우 이 수치는 달라 집니다. 귀찮은 분들은 시간이 걸리더라고 calcGyroOffsets()함수로 통일해도 되겠죠.

mpu6050.setGyroOffsets(0.75, 0.05, 0.05);

어려분들은 둘중 하나를 선택해서 실험 하시면 됩니다.

mpu6050.calcGyroOffsets(true); : 대기 시간이 길어지더라도 계산하겠면 선택
mpu6050.setGyroOffsets(0.75, 0.05, 0.05); : 처음 한번만 계산해 놓고 그 값을 초기값으로 고정하겠다면 선택

2) processing 코딩


참고 : [아두이노] 조이스틱+processing 3D 도형 회전(1)


[참고 소스]

import processing.serial.*;

Serial myPort;
int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
     
  ... 생략 ...
}

조이스틱으로 실험했던 일부 소스입니다. box를 x,y 회전을 시킨 소스의 일부분인데 오늘 사용할 부분만 제외하고 나머지 소스를 지웠습니다.

processing 문자열 읽기

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
     
  ... 생략 ...
}

이렇게 코딩해서 시리얼통신에서 문자열일 들어오면 자동으로 이벤트 호출이 일어납니다. 그리고 해당 시리얼포트로 문자열을 읽게 됩니다. 여기까지는 아두이노에서 코딩하는 방식과 같습니다. 하지만 processing에서는 링크 걸어놓은 String 방식으로 코딩을 하지 않고 간단하게 processing 함수를 이용하여 수정하겠습니다. split()함수가 제공됨으로 쉽게 전체문자열에서 x,y,z 회전각을 분리해 낼 수 있습니다.

String[] inStrings = split(inString, ',');

inString으로 읽은 한줄의 문자열에서 콤마(,)가 기준으로 쪼개집니다. 그리고, 그 값은 문자열 배열 변수 inStrings에 저장되게 됩니다. 3개의 데이터를 전송하니깐

inStrings[0] => X축 회전
inStrings[1] => Y축 회전
inStrings[2] => Z축 회전

이 값들이 저장되어 있게 됩니다. 하지만 이 값은 문자열이기 때문에 숫자형으로 변환해야 하는데 실수형 float()로 변환을 하면 다음과 같습니다.

x=float(inStrings[0]);
y=float(inStrings[1]);
z=float(inStrings[2]);

아두이노에서 시리얼통신으로 보내 온 x,y,z값을 각각 실수형 x,y,z변수에 최종적으로 저장하게 됩니다.

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');    
  String[] inStrings = split(inString, ',');
  x=float(inStrings[0]);
  y=float(inStrings[1]);
  z=float(inStrings[2]);  
}

수신 String 중 회전각만 적용하기

문자열 읽기를 그냥 위와 같이 하면 에러가 발생합니다. 그 이유는 MPU6050_tockn 라이브러리는 초기 세팅함수 안에는 다음과 같이 여러 문자열이 출력됩니다.


회전 각이 나오기 전까지 여러문장의 문자열이 출력되는데 단순위 위와같이 코딩하면 문제가 발생하겠죠. MPU6050_tockn라이브러리에 가서 cpp 파일에 해당 함수의 들어있는 print문을 전부 주석처리하면 해결할 수 있지만 라이브러리 파일을 건들면 좀 그렇겠죠.

그래서 회전각 문자열인지 체크하는 제어문을 넣어서 해결하도록 하겠습니다.

String inString = myPort.readStringUntil('\n');   
int separator_ch = inString.indexOf(",");

if(separator_ch!=-1){
  회전각 구하기;
}else{
 print(inString);
}

이렇게 indexOf(',')로 회전각 분리기호의 위치값을 찾습니다. 회전각 문자열에는 분리기호가 있기 때문에 해당 0이상의 숫자값을 갖게 됩니다. 하지만 해당 문자열에 분리기호가 없다면 '-1'이 반환 됩니다. 이 원리를 이용해서 위와 같이 if문으로 분리기호가 있으면 방금 위에서 코딩한 문자열에서 회전각을 분리해내는 로직을 붙이면 됩니다. 분리기호가 없다면 그냥 시리얼모니터 창으로 해당 문자열을 출력하게 하면 간단히 이 문제를 해결하게 됩니다.

box 회전시키기

box 회전을 하려면 이 부분을 수정해야 합니다. rotateX(), rotateY(), rotateZ() box 회전을 시키고 해당 인자값을 아까 String으로 읽은 값을

rotateX(radians(y)); //x축 회전
rotateY(radians(x)); //y축 회전
rotateZ(radians(z)); //y축 회전
    
box(200,100,200); //상자

x,y,z 축으로 회전시켜서 box()을 배치하는 코딩입니다. box()함수 앞에다가 회전함수를 넣으면 최종적으로 회전시킨 곳에 box가 그려지기 때문에 box가 x,y,z 값에 의해서 이동하는 것처럼 보이게 됩니다.

그리기 함수부분은 아래과 같이 되겠죠.

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

[processing 실험 소스]

import processing.serial.*;

Serial myPort;

float x=0;
float y=0;
float z=0;

void setup() {
    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 19200);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');   
  int separator_ch = inString.indexOf(",");
  if(separator_ch!=-1){
    String[] inStrings = split(inString, ',');
    x=float(inStrings[0]);
    y=float(inStrings[1]);
    z=float(inStrings[2]);
  
    print(x);
    print('/');
    print(y);
    print('/');
    println(z);
  }else{
    print(inString);
  }
}

추가로, print문으로 정확히 분리되어 x,y,z값이 나오는지 확인하기 위해서 processing 시리얼 모니터로 출력시키는 코딩을 넣었습니다.

5. 실험 결과


MPU-6050 모듈을 움직일 때 PC로 녹화한 영상입니다.



MPU-6050 모듈을 움직일 때 processing의 box가 어떻게 움직이는지 폰을 촬영한 영상합니다.



마무리


MPU-6050 라이브러리를 이용하면 해당 회전각을 가지고 여러분들이 원하는 표현을 쉽게 할 수 있습니다.

참고로, 주의할 점은 될 수 있으면 MPU-6050 모듈에 핀을 납땜해주시고 실험 해주세요. 그리고 핀을 연결한 선도 단단히 고정시켜주세요. 조금만 흔들려도 MPU-6050이 먹통이 됩니다. 그리고, 회전 각에 크게 변화할 때에도 한번 먹통이 발생하면 더이상 측정하기는 불가능합니다. 그리고 일정시간이 지나면 값의 변화가 생기기 때문에 정교한 제어용으로 쓰기에는 다소 부족합니다. 초보 입문용으로 싼 가격에 자이로센서를 사용하는데 가성비를 최고이지만 혹시 드론 같은 전문적으로 사용할시에는 비싼 자이로센서를 쓰기기 바랍니다.

그리고, 어느정도 재미를 붙이시면 제대로 회전각을 구하는 공식을 공부해주시기 바랍니다. 사실 기존 라이브러리로만 사용해서는 어떤 실험을 할지 모르지만 정교한 제어는 어렵습니다. 그냥 무조건 값을 읽고 계산해서는 정교한 제어가 되지 않습니다. 초기 보정작업과 기타 값의 변화율에 대한 처리부분을 고려해서 코딩해야 합니다. 수학적으로 접근해야 하는 부분들이 많기 때문에 전문서적이나 구글검색을 통해 관련 자료들을 많이 찾으셔서 공부하시기 바랍니다.

저는 기초적인 부분만 다루고 실험했기 때문에 좀 깊은 공부는 아직은 관심이 덜해서 해보지는 않아서 좀 더 자세한 내용을 전달해드리지 못하네요. 제 경우는 라이브러리 코딩쪽이 아니라 측정된 값을 기반으로 제어하는 코딩에 좀 더 관심이 많아서 그냥 라이브러리를 그대로 사용하고 있네요

자이로센서를 전문적으로 다루기 위해서는 배워야 할 부분이 많지만 간단히 실험하고 기초적인 기능만 활용하시더라도 라이브러리를 이용해 원하는 표현은 가능하니깐 MPU-6050 모듈을 배워 보세요.


댓글()

[아두이노] MPU-6050 가속도/자이로 센서 제어

IOT/아두이노|2019. 5. 12. 09:00

[아두이노] MPU-6050 가속도/자이로 센서 제어 



오늘은 MPU-6050 가속도/자이로 센서를 이용하여 실험하는 시간을 갖도록 하겠습니다. 자이로 센서를 사용하려면 오일러의 공식을 알아야 하고 오일러의 공식 이해하셔야 하고 각속도와 오일러의 변화율 공식과 오일러의 각을 이해하기 위해서 피치, 롤, 요와 같은 용어적 의미와 공식을 알아야 합니다. 그리고 회전각을 구할 때 대표적인 2개의 필터가 있는데 상보필터와 칼만필터 중 하나 정도는 공식을 알아야 합니다. 이처럼 수학적인 공식을 알아야 제대로 사용할 수 있습니다. 나중에 깊게 공부할 때는 모두 알아야 할 내용인데 입문자에게는 버거운 내용입니다. 하지만 이 모든것을 몰라도 사용할 수 있습니다. MPU-6050라이브러리르 사용하면 쉽게 회전각을 구할 수 있습니다. 바로 라이브러리를 사용하는 것보다 먼저 간단히 어떤 값들이 MPU-6050 모듈을 통해 측정되는지 살펴봐야 겠지요. 우선, 간단히 MPU-6050에 대해서 살펴보고 실험해보도록 하죠.


1. MPU-6050 가속도/자이로 센서


MPU-6050 모듈은 가속도/자이로를 측정할 수 있는 센서입니다. 가속도는 지구 중력을 기준으로 x, y, z 축의 가속도 크기를 구할 수 있으면 자이로(각속도)는 시간당 x, y, z 축의 회전속도 속도를 구할 수 있습니다.


MPU-6050 모듈에서 측정된 값은 바로 회전각으로 이해하기 힘듭니다. 그래서 계산이 필요하는 데 오일러의 각 공식을 알아야 합니다. 아래 링크된 위키백과사전에서 한번 읽어주시기 바랍니다.

3차원 회전 좌표계로 X축 회전을 롤, Y축 회전을 피치, Z축 회전을 요라고 합니다. 롤,피치,요에 대한 계산 공식이 따로 있습니다.


상보필터과 칼만필터는 회전각을 구할때 필요한 필터입니다.

아래는 상보필터의 공식입니다.

칼만필터은 아래 위키백과에 가셔서 공식을 살펴보시기 바랍니다.
https://ko.wikipedia.org/wiki/%EC%B9%BC%EB%A7%8C_%ED%95%84%ED%84%B0

대표적으로 이 두개의 필터로 회전각을 구하게 되는데 나중에 실험에서는 상보필터로 된 라이브러리를 이용할 예정입니다.

아래 MPU-6050 데이터 시트에 가셔서 MPU-6050에 대해서 알아두시기 바랍니다.
https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf

2. MPU-6050 가속도/자이로 센서 구조


MPU-6050 모듈은 가속도 (x,y,z)와 자이로(각속도) (x,y,z)의 값과 온도 값을 얻을 수 있습니다.


MPU-6050 모듈은 I2C 통신으로 14개의 레지지스터 값을 아두이노로 보내고 데이터는 16bit로 구성된 총 7개의 데이터를 얻게 됩니다.


MPU-6050 모듈의 핀에 대해서 위표를 잘 살펴보시고 인터럽트핀은 아두이노우노의 경우는 인터럽트 핀이 2번이기에 사용하실 경우 2번 핀에 연결하시면 되고 사용하는 아두이노보드에 따라 해당 I2C핀과 인터럽트 핀에 맞게 연결하시면 됩니다.

3. MPU-6050 가속도/자이로 회로도


  • 준비물 : MPU-6050 모듈, 아두이노우노
  • 내용 : I2C 통신을 위해 A4(SDA), A5(SCL)핀에 연결하시오

I2C핀만 주의해서 연결하시면 됩니다.


4. 코딩



우선 기본 라이브러리 없이 순수 MPU-6050 모듈에서 측정되는 값이 어떤 값들이 출력되는지 살펴보도록 하겟습니다.

위 링크된 MPU-6050소스에 대해 간단히 살펴보겠습니다.

[소스]
// By Arduino User JohnChi

#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.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);
}

MPU-6050의 I2C 주소는 '0x6B'입니다.

int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;

16bit int형 자료형으로 가속도 3개, 온도 1개, 자이로 3개의 변수를 선언합니다.

  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);

beginTransmission()함수로 '0x6B'주소로 I2C 슬레이브 디바이스로 전송을 시작합니다. 그리고 버스가 연결되면 '0x6B' 보내고 다시 '0'을 보내고 나서 endTransmission(true)함수로 버스를 해제하는 메세지를 보냅니다. 초기화 수행합니다.

Wire.beginTransmission(MPU_addr); //MPU-6050 '0x6B'주소 시작
Wire.write(0x3B); // 0x3B 읽을 rigister주소
Wire.endTransmission(false) //버스를 연결 활성화
Wire.requestFrom(MPU_addr,14,true) //데이터 요청하는데 14 레지스터 값을 요청

이렇게 해서, Wire.read()함수를 이용해서 실제로 MPU-6050에서 값을 읽게 됩니다. 0x3B~0x48의 레지스터값을 읽어와서 가속도, 온도, 자이로 값을 만들어 내게 됩니다.

Wire.read()<<8|Wire.read();

'0x3B' 레지스터 값을 왼쪽으로 8bit 이동시키고 '0x3C' 레지스터 값을 비트OR 연산을 수행합니다. 이 식을 통해서 2개의 레지스터 값을 합치게 됩니다.

예를 들어 임의의 값 A, B가 있을때 위 식으로 계산한다면


더 쉽게 살펴보면은, 비트OR 연산은 둘중 하나가 1이면 결과가 1이 나오는 연산입니다.


이렇게 14개의 레지스트값을 2개씩 이 수식을 통해서 가속도(x,y,z), 온도, 자이로(x,y,z)값을 순차적으로 Wire.read()함수를 14번 읽어서 연산을 수행하여 값을 얻게 됩니다. 그 값을 시리얼모니터로 출력하기 때문에 순수 MPU-6050 모듈에서 추출한 값입니다.
[결과]


5. 결과


실제 영상으로 살펴보면 MPU-6050 가속도/자이로센서를 움직일 때마다 가속도 AcX, AcY, AcZ 값과 자이로 GyX, GyY, GyZ 값이 나옵니다. 추가로 Tmp(온도)값은 온도를 계산식이 간단해서 값으로 나오고 가속도와 자이로는 우리가 각도로 느끼기에는 다소 어려운 수치로 출력이 이뤄집니다. 이 값으로 나중에 계산해서 회전각을 구하게 되는데 우선 어떤 값들이 찍히는지는 알아야 계산을 할 수 있겠죠. 나중에 위 링크된 곳에 가셔서 한번 공식에 대해서 자세히 배워보시기 바랍니다.


마무리


오늘 실험은 실제 회전각을 공식의 의해 구한 값이 아니라 MPU-6050의 값을 읽어온 값을 시리얼 모니터로 출력만 해서 뭔가 와닿지 않을 것 같네요. 다음 post는 MPU-6050 라이브러리를 이용해서 좀 더 쉽게 접근하고 실제 X, Y, Z 회전각을 구한 값을 통해서 간단히 실험해보면 자이로센서가 좀 더 친근하게 다가 올 듯 싶네요.

오늘은 좀 어렵더라도 대충 이런식으로 MPU-6050 모듈의 값을 읽는구나 정도로 이해하시고 넘어가시면 되겠습니다.


댓글()