[라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 3.5 1축 짐벌 만들기

메카 2017-11-20 (월) 23:09 10개월전 709  


 

라즈베리파이 강좌

  안드로이드 씽스(Android Things) 시작하기 - 3.5 1축 짐벌 만들기






  안녕하세요. 메카솔루션 알도입니다.

  지난 포스트에서 MPU6050 값을 읽는 방법과, 서보모터 사용법을 알아보았습니다. 이번 포스트에서는 이 두가지를 합쳐서 1축 짐벌을 만들어보겠습니다. 짐벌이란 카메라 등 일정한 방향을 향해야 하는 장치를 사용할 때 자이로 또는 기울기 센서를 이용해 몸체의 회전을 감지하고,  이를 보정해주는 장치를 말합니다. 카메라 짐벌은 흔하게 볼 수 있으며 주로 사람이나 드론 동영상 촬영에서 움직임이 많을 때 사용합니다.



  극적인 예로는 전차의 주포에 연결된 경우입니다. 전차는 주로 야지에서 움직이기 때문에 주행 중 흔들림이 심한데, 짐벌이 도입되기 전에는 이동하면서 사격하는 것이 불가능 했습니다. 하지만 전차의 주포에 짐벌과 기타 조준 보조 장치가 도입되면서, 매우 빠른 속도로 달리면서도 주포가 일정한 방향을 향할 수 있게 되면서, 이동 중 사격이 가능하게 되었습니다. 또 다른 예는 선박에서 위성 통신을 하고자 할 때, 위성 안테나가 일정하게 위성을 향해 있어야 하는데, 전차와 마찬가지로 선박은 자체의 이동이나 파도의 영향으로 흔들림이 발생하기 때문에, 위성 안테나에도 짐벌을 장착하여 선박이 아무리 흔들려도 위성을 향해 있을 수 있도록 해줍니다.


 


 

     1축 짐벌 예제


 준비물

  이번 강좌를 위해서는 다음과 같은 준비물들이 필요합니다.


    - 라즈베리파이 3

    - Micro USB 전원

    - 모니터(필수는 아닙니다.)

    - 다음 셋 중 하나는 반드시 필요합니다.

시리얼 컨버터

공유기 + 랜선

랜카드(이더넷 포트) + 랜선


 

 

 

  GY-521 MPU6050 모듈

 SG90 서보모터

 M-F(수-암) 점퍼

  M-M(수-수) 점퍼


  안드로이스 씽스를 준비하는 방법은 아래 포스트를 참고하세요.

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 1.1 시리얼로 초기화하기

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 1.2 공유기를 통해 초기화하기

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 1.3 직접 연결하여 초기화하기


  초기 연결과 프로젝트 생성/업로드 부분은 아래를 참고하세요.


    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 2. Hello World


  지난 포스트인 LED를 주기적으로 켜고 끄기는 아래를 참고하세요.


    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 3.1 LED & 버튼 예제

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 3.2 LED & 버튼 예제

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 3.3 서보모터 예제

    [라즈베리파이 강좌] 안드로이드 씽스(Android Things) 시작하기 - 3.4 관성센서(MPU6050) 예제



 회로 연결하기

  아래와 같이 MPU6050과 서보모터를 연결합니다.


 




 코드 작성하기

  완성된 프로젝트는 깃허브에서 다운로드 받을 수 있습니다.

    https://github.com/JaewonAC/mpuServo1


 그래들 설정

  dependencies에 다음을 추가합니다.


dependencies {

    ...

    provided 'com.google.android.things:androidthings:0.5.1-devpreview'

    compile 'com.google.android.things.contrib:driver-pwmservo:0.3'

    compile 'com.mechasolution:mpu6050:0.2'

}

 


매니페스트 설정

아래와 같이 <application .... >이 끝나는 부분에 아래 코드를 입력합니다.

<application>

    <uses-library android:name="com.google.android.things"/>


    ...

</application>

 


  서보 모터 제어를 위해 Servo 클래스 객체와 MPU6050의 값을 읽어오기 위해 mpu6050, 이들을 주기적으로 사용하기 위해 쓰레드 클래스 객체를 선언해줍니다. 매번 측정되는 각도는 angle에 저장되며, 적분에 사용하기 위해 지난 번 루프의 시간을 timepre에 저장합니다. While문 내부는 후에 채우도록 하겠습니다.


float angle = 0;

long timepre = 0;

private Servo mServo;

private mpu6050 mMpu = new mpu6050();

Thread mThread = new Thread() {

    public void run() {

        while (true) {

            // put your main code here, to run repeatedly:

        }

    }

};

 


  서보 클래스 객체와 MPU6050 클래스 객체는 다음과 같이 초기화 합니다. 서보는 최초 위치가 0도가 되도록하며, 모터가 회전하는 시간 여유를 위해 1초를 대기합니다.

  앞서 정리한 서보 모터 제어 방법을 다시 정리해보면, PWM의 주파수는 50㎐, 주기가 20㎳에 PWM의 폭이 1㎳ 일 때 -90도, 2㎳ 일 때 90도, 1.5㎳ 일 때 0도 입니다. 이것을 서보 클래스를 초기화할 때 펄스폭은 1~2㎳, 각도 폭은 -90~90도로 설정해줍니다. 문제는 펄스폭 부분이 정확히 맞지가 않습니다. 심지어 같은 SG90 서보 모터인데도 서로 조금씩 다릅니다. 아래 코드는 필자가 값을 조금씩 바꿔보면서 맞춘 것으로 펄스폭이 원래 1~2㎳이어야 했던 것을 0.75~2.6㎳로 조정했습니다. 여러분들도 실제 하시려면 값을 조금 조정하셔야 할 것입니다.


try {

    mServo = new Servo("PWM1");

    mServo.setPulseDurationRange(0.75, 2.6);

    mServo.setAngleRange(-90, 90);

    mServo.setEnabled(true);

    mServo.setAngle(0);

} catch (IOException e) { e.printStackTrace(); }


try {

    mMpu = new mpu6050();

    mMpu.open();

} catch (IOException e) { e.printStackTrace(); }


try {

    Thread.sleep(1000);

} catch (InterruptedException e) { e.printStackTrace(); }


mThread.start();


 


  처음 멤버 객체/변수 작성 부분에서 비워놓았던 While 문 내부를 여기서 채우도록 하겠습니다. Servo 클래스의 setAngle 함수를 이용해서 서보 모터의 각도값을 입력해줍니다. 아래와 같이 쓰레드 함수 내부를 작성해 줍니다.


Thread mThread = new Thread() {

    public void run() {

        timepre = System.nanoTime();

        while (true) {

            try {

                long timecur = System.nanoTime();

                float GyroZ = mMpu.getGyroZ();

                float dt = (float)((timecur - timepre) / 1000000000.);

                timepre = timecur;

                angle -= GyroZ * dt;

                angle = Math.max(-90, (Math.min(90, angle)));


                mServo.setAngle(angle);


                Log.i(TAG, String.format("Time = %f Angle = %f", dt, angle));

            } catch (IOException e) { e.printStackTrace(); }


            try {

                Thread.sleep(10);

            } catch (InterruptedException e) { e.printStackTrace(); }

        }

    }

};

 


  System.nanoTime()은 나노 단위로 시간을 측정하는데 사용되는 함수입니다. 루프가 한바퀴 도는 시간을 측정하며, 이 시간을 각속도에 곱한 것이 적분이며, 회전한 각도가 되게 됩니다. 시간을 측정하면 현재시간 timecur를 이전 시간 timepre에 저장합니다. 서보 클래스 객체는 각도범위를 넘는 값을 입력하면 에러가 나서 멈추게 되므로 30번 줄과 같이 각도값이 -90보다 작거나 90보다 크게되지 않도록 해줍니다.

  전체 코드는 아래와 같습니다.


import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import com.google.android.things.contrib.driver.pwmservo.Servo;

import java.io.IOException;

import mechasolution.mpu6050.mpu6050;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();


    float angle = 0;

    long timepre = 0;

    private Servo mServo;

    private mpu6050 mMpu;

    Thread mThread = new Thread() {

        public void run() {

            timepre = System.nanoTime();

            while (true) {

                try {

                    long timecur = System.nanoTime();

                    float GyroZ = mMpu.getGyroZ();

                    float dt = (float)((timecur - timepre) / 1000000000.);

                    timepre = timecur;

                    angle -= GyroZ * dt;

                    angle = Math.max(-90, (Math.min(90, angle)));


                    mServo.setAngle(angle);


                    Log.i(TAG, String.format("Time = %f Angle = %f", dt, angle));

                } catch (IOException e) {

                    try {

                        mMpu.close();

                        mMpu.open();

                    } catch (IOException e1) { e1.printStackTrace(); }


                    try {

                        Thread.sleep(1000);

                    } catch (InterruptedException e1) {

                        e1.printStackTrace();

                    }

                    e.printStackTrace();

                }


                try {

                    Thread.sleep(10);

                } catch (InterruptedException e) { e.printStackTrace(); }

            }

        }

    };


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);


        try {

            mServo = new Servo("PWM1");

            mServo.setPulseDurationRange(0.75, 2.6);

            mServo.setAngleRange(-90, 90);

            mServo.setEnabled(true);

            mServo.setAngle(0);

        } catch (IOException e) { e.printStackTrace(); }


        try {

            mMpu = new mpu6050();

            mMpu.open();

        } catch (IOException e) { e.printStackTrace(); }


        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) { e.printStackTrace(); }


        mThread.start();

    }

}




 결과



모바일 버전으로 보기