2015. 10. 13.

DIY 3axis gimbal - 1 ( MPU 6050 Sensor로 3축 각도 알아내기 )

<구글에서 '3 axis gimbal'을 검색한 결과>
<Search result for '3-axis gimbal' on Google>

 동영상을 흔들림 없이 촬영하려면 어떻게 해야 할까? 물론 사람이 조심조심하면서 촬영할 수 있겠지만 힘들것이다. 그러나 3축 짐벌이라는 도구를 사용하면 흔들림 없이 깨끗한 촬영이 가능하다. 액션캠이 대중화 되면서 3축 짐벌 또한 수요가 증가하고 있다. 나 역시 깨끗한 촬영을 하고 싶었고 3축 짐벌의 가격을 찾아보았다. 하지만 3축 짐벌은 그 가격이 매우 비싸다...

 How do we shoot a video without shaking? Of course, you can shoot a video by your hands. But it will be very hard. However, you can easily shoot video without shaking by using a tool called  '3-axis gimbal'. As the action cam's popularity has increased, 3-axis's demand has increased too. I also wanted a clear video and search a price of 3-axis gimbal. However, the price was very very expensive...

그럼 내가 만들면 되지 뭐.

Well.. Maybe D.I.Y is better.



<나만의 목표>

1. 먼저 목표 가격은 인건비를 제외하고 재료비만 20만원 아래로 할 것이다. 
2. 모터는 서보모터를 이용한다.
3. MPU 6050 센서를 이용하므로 100만원대 제품 정도의 정밀도는 아니지만 가격대비 좋은 성능을 내도록 만든다.  

<My own goal>

1. Price of material is lower than 173 dollars.
2. Using digital servo motor
3. MPU 6050 is very cheap sensor. So I will do my best for good performance.


<MPU 6050 sensor>

 MPU 6050은 3축 가속도 센서+자이로 센서로 이루어진 센서로 2가지 값을 센싱할 수 있다. 

▲ 가속도 센서는 중력가속도를 기반으로 X,Y,Z축에 대한 직선 가속도를 측정한다. 외력에 의해 측정값이 왜곡될 수 있지만 시간이 흘러도 오차가 커지지 않는 다는 장점이 있다. 

▲ 자이로 센서는 X,Y,Z축의 각속도 변화량을 측정한다. 그런데 각도의 미분값 = 각속도이므로 각도를 알기 위해서는 이 값을 적분해줘야 한다. 그런데 적분을 하게 되면 누적오차가 쌓이기 마련이다. 가속도 센서보다는 실제 센서의 움직임과 같은 값을 내지만 누적 오차 때문에 시간이 흐를 수록 오차가 커지게 된다.

 따라서 필터를 이용해 이 둘의 장점만을 따오는 식으로 사용한다. 즉 실제 센서의 움직임과 같으면서 누적오차가 없는 결과가 나오게 되는 것이다. 보통 계산 주기 때문에 계산량이 상대적으로 가볍고 만들기 쉬운 상보필터를 많이 쓰는데, 칼만필터를 적용한다면 더 좋다고 한다. (여기서 다시 확인하는 칼만필터의 위력) 그러나 칼만필터는 계산량이 많아 계산 주기가 길어지기 때문에 Arduino Uno와 같은 보드로는 실시간제어가 힘들다는 문제가 있다. 어찌되었든 코드를 찾는게 문제였다.

 MPU 6050 is a sensor that made of 3-axis acceleration sensor and 3-axis gyro sensor. So MPU 6050 can measure acceleration and gyro together.

The acceleration sensor is based on gravity. It measure an acceleration from each X,Y,Z axis. An acceleration value may be distorted by an external force, but acceleration sensor has an advantage that the error does not increase overtime.

The gyro sensor measure a angular speed from each X,Y,Z axis. However, because of angular speed is differential of angle, we have to integrating this value. But the problem is because of integration, there is an accumulated error. The gyro sensor could measure almost corrected value from real movement. But it still have an accumulated error.

 So we have to use a filter for taking each advantage from 2 sensors. If we use a filter, we can always get a correct value from MPU 6050. Kalman filter is a popular filter. But it could heavy to arduino. So I have to find another solution from internet.



하지만 언제나 Google에는 해결책이 있다..!!

But there is always a solution in Google!!




 아래는 MPU 6050과 관련된 자료를 찾다가 발견한 사이트이다. 이는 아두이노 공식 홈페이지에서도 추천하는 방법이다.

 먼저 i2cdevlib 를 다운 받는다. 그리고 그 폴더안에 Arduino라는 폴더가 있다. Arduino 폴더 안에 있는 'MPU6050' 폴더와 'I2Cdev' 란 폴더를 원래 아두이노가 설치된 폴더의 libraries 폴더 안에 복사한다. 라이브러리를 추가하기 위함이다. 
 그리고 예제코드인 MPU6050_DMP6.ino 를 다운받아 컴파일을 해본다. 오류 없이 잘 된다면 일단 소프트웨어는 준비가 완료되었다. 이 예제코드는 센서안에 있는 DMP(Digital Motion Processor)로 부터 가속도와 자이로값을 토대로 쿼터니안값을 계산하고 이것을 이용해 원하는 값으로 변환하여 내뱉는 코드이다. Jeff Rowberg란 사람이 i2cdevlib란 라이브러리를 만들어 놓아서 그걸 이용해 만들었다고 한다. 

 코드 내용을 보면 쿼터니안 값, 오일러 각도 값, 가속도 각도 값, yaw pitch roll 값 등을 출력하는 부분이 있으니 필요한 부분을 쓰면 될 듯 하다. 나는 3축짐벌을 만들어야 하므로 yaw, pitch, roll 값을 오일러 값으로 출력하는 부분을 이용했다. 아래는 내가 수정한 부분이다. 

 Below is the link that I found. That solution is recommended by Arduino website.

 First, we have to add some libraries. Download i2cdevlib. And there is a folder named 'Arduino'. In 'Arduino' folder, there is 'MPU6050' folder and 'I2Cdev' folder. Copy 'MPU6050','I2Cdev' folder and paste in 'libraries' folder that is inside your installed arduino IDE folder. 
 And download an example code MPU6050_DMP6.ino and compile. If there is no an error, the basic thing is done. 

 In this example code, you can select output value by #define part. You can get quaternion, euler angle, yaw pitch roll value, etc.. And I use yaw pitch roll value from an example code. And below is edited part.



<Servo motor SETUP> - 서보모터 셋업

// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================
int servoPin1 = 3, servoPin2 = 5, servoPin3 = 11;
Servo servo1, servo2, servo3;
void setup() {
        servo1.attach(servoPin1); 
        servo2.attach(servoPin2);
        servo3.attach(servoPin3);
        servo1.write(0); //Init the servo1 angle to 0
        servo2.write(0); //Init the servo2 angle to 0
        servo3.write(0); //Init the servo2 angle to 0
cs



<Control Servo motor> - 각 각도값에 따라 서보모터 컨트롤

        #ifdef OUTPUT_READABLE_YAWPITCHROLL
            // display Euler angles in degrees
            mpu.dmpGetQuaternion(&q, fifoBuffer);
            mpu.dmpGetGravity(&gravity, &q);
            mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
            Serial.print("ypr\t");
            Serial.print(ypr[0* 180/M_PI);
            servo1.write(map(ypr[0* 180/M_PI, -1801800180)); //Control servo1
            Serial.print("\t");
            Serial.print(ypr[1* 180/M_PI);            
            servo2.write(map(ypr[1* 180/M_PI, -90900180)); //Control servo2
            Serial.print("\t");
            Serial.println(ypr[2* 180/M_PI);
            servo3.write(map(ypr[2* 180/M_PI, -90900180)); //Control servo3
         
        #endif
cs



<Yaw, Pitch, Roll angle, from wikipedia > 

  그런데 ypr[0] (=Yaw)는 -180~+180이 되는데, ypr[1], ypr[2] (=Pitch, Roll)는 -90~+90 밖에 측정이 되지 않으므로 이에 맞게 서보모터가 움직일 수 있도록 map()을 사용하였다. 

 ypr[0] (=Yaw) can measure -180~+180. But ypr[1], ypr[2] (=Pitch, Roll) is only -90~90. So I used map().




<MPU 6050 핀 연결>

<MPU 6050 pin map>

▲ 5V <->VCC (You can also connect with 3.3V)
▲ GND <-> GND
▲ A5 <-> SCL
▲ A4 <-> SDA
▲ Digital pin 2 <-> INT (interrupt)

※나머지 핀은 연결하지 않는다.
※The remaining pins are not connected.


<dccDuino>

 Arduino Uno의 클론 보드인 dccDuino 이다. 마이크로 칩을 사용했다는 것과 암컷핀이 수컷핀으로 확장되어 암,수 핀이 모두 연결가능하며 전원핀도 확장되 었다는 것이 차이점이다. 인터넷에서 할인하여 5천원대에 구매했다. (정품의 약 1/6가격.. 그러나 성능은 동일하다. 왜 그런지는 밑에 나올 동영상을 보면 알게 될 것이다.) 다만 입력 전원이 조금 낮다는 점과 전용 드라이버를 다운받아야 PC에서 인식이 된다. 드라이버는 인터넷에서 쉽게 구할 수 있다. 귀찮으면 아래 링크로 다운로드 받자.








<데모 영상>


<MPU 6050 센서로 서보모터 컨트롤 하기>
<Servo motor control with MPU 6050 sensor>

 영상을 촬영하기 전 Yaw값이 steady state가 될 때까지 기다린 후 촬영하였다.(30초 정도 소요) 한번 steady state가 되면 그 이후로는 안정적인 값을 출력한다. Arduino Uno급 보드로도 충분히 정밀한 제어가 가능하다는 것을 알 수 있다.

 Before taking this video, I waited for Yaw value until steady state.(It takes maybe 30 seconds) In this video, we can sufficient accurate control with arduino-level board.
















<번외편>

<Extra>


<Hot Hand USB Wireless MIDI Controller>

 위 제품 영상중 2:02초 부터 나오는 장면을 보면 각축의 기울기에 따라 mapping된 knob값이 변경되는 것을 볼 수 있다. 
 저걸 보자마자 드는 생각..

 In that video from 2:02 seconds, the product control knobs by each axis angle.



저거 나도 충분히 가능한거 아냐?!

I think I can make like that!




  결론적으로 말하자면 가능하다. 물론 무선통신을 하는 건 블루투스 모듈이나 Xbee 모듈이 있어야 하므로 나중에 만들더라 치더라도 일단 기울기로 knob값 조절 기능은 가능하다.

 In conclusion, it is possible.



<MIDI Control>

        #ifdef OUTPUT_READABLE_YAWPITCHROLL
            // display Euler angles in degrees
            mpu.dmpGetQuaternion(&q, fifoBuffer);
            mpu.dmpGetGravity(&gravity, &q);
            mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
            //Serial.print("ypr\t");
            //Serial.print(ypr[0] * 180/M_PI);
            midiControl(0xB01, map(ypr[0* 180/M_PI, -1801800127));
            delay(10);
            //Serial.print("\t");
            //Serial.print(ypr[1] * 180/M_PI);
            midiControl(0xB02, map(ypr[1* 180/M_PI, -90900127));
            delay(10);
            //Serial.print("\t");
            //Serial.println(ypr[2] * 180/M_PI);
            midiControl(0xB03, map(ypr[2* 180/M_PI, -90900127));
            delay(10);            
        #endif
cs

 위에서 Yaw, Pitch, Roll 값을 이용해 서버모터를 제어하던 부분에 'midiControl' 이란 함수로 변경하였다. 각각 PC 프로그램에서 1,2,3 채널로 인식이 되어 control 값이 입력이 된다. control 값은 0~127사이의 값을 가져야한다.

 Instead of controlling the server motor using Yaw, Pitch, and Roll values, I changed the function to 'midiControl'. It have 1,2,3 channel and input control value to computer. control value should 0~127.

<midiControl function>

void midiControl(int st, int cc, int val) {
  Serial.write(st);
  Serial.write(cc);
  Serial.write(val);
}
cs

  midiControl function은 MIDI 규격으로 출력하게 해주는 역할이며 첫째 인자는 보내는 값이 음인지 컨트롤 값인지를 구분하는 것이고 두번째 인자는 각 음의 높이 혹은 컨트롤 채널을 의미하며 마지막 인자는 음일 경우 속도를, 컨트롤일 경우 값을 의미한다. 그러나 USB를 이용해 PC와 serial 통신으로 MIDI 값을 전송하는 것이기 때문에 중간에 2 가지 프로그램이 필요하다.

 'midiControl function' make a MIDI serial data. 'st' is distinguish whether sending data is key or control. 'cc' is key level or control channel. 'val' is velocity or control value. But arduino uno use a usb for serial communication with computer. So we need 2 more program.






 첫째로는 아두이노와 같은 디바이스로 부터 받은 serial 값을 MIDI 통신 값으로 전달하는 다리 역할을 하는 프로그램이다.
'hairless-midiserial'이란 프로그램이며 리눅스, 맥, 윈도우 버젼이 있다.

First program is 'hairless-midiserial'. It change serial data to MIDI data. 
This program is available for Linux, Mac, and Windows versions.





Link : loopmidi

 두번째로는 가상 midi port 역할을 하는 'loopmidi'란 프로그램이다. 윈도우버젼만 있다.

Second program is 'loopmidi'. It is a virtual midi port. Mac does not need a virtual midi port.(Because Mac support this thing)





자, 신호가 가는 순서를 정리하자면 이렇다.
Well, the signal flow like below.

MPU6050  ->  Arduino  ->  hairless-midiserial  ->  loopmidi  ->  Abletonlive



<데모 영상> - Demo

<MPU 6050 센서로 Abletonlive 입력하기>
<Control Abletonlive with MPU 6050>

각 축에 따라 노브값이 바뀌는 것을 알 수 있다.
Abletonlive의 임의의 knob와 MIDI 신호를 mapping 하는 것은 youtube에 많이 나와 있다.
아두이노에서 보내는 MIDI 신호 값을 Abletonlive에서 mapping 시키는 작업은 필수이다. 

It can be seen that this knob value is changed by each axis angle.
How to mapping of a knob with MIDI signals are a lot of on youtube.
In Abletonlive, it is necessary to map the MIDI signal values sent by Arduino.

2015. 10. 1.

성곽길


<성곽길>
<성곽길>


 2주년을 맞이하여 다녀온 성곽길.
성곽길이 혜화문부터 시작한다고 쓰여있지만.. 사실은 그 건너편에서 출발해야 한다.

너무나 맑고 쾌청한 가을하늘이었다. 언덕이 많았지만 성벽 때문에 생긴 그늘 덕분에 덥지않았다.