Hardware | Arduino 로 Gimbal 컨트롤 하기

|

1. 길고 긴 시작


때는 2015년, 한창 Kickstarter 에 열을 올려, 이것 저것에 투자(?) 하고 있을 때,

당시 저에게는 너무 생소한 Gimbal 이라는 것을 접하게 됩니다.


지금이야 드론이나 스포츠 경기 촬영, 또는 일반인이 손으로 가지고 다니면서 흔들림 방지 해주는 분야에까지 널리 사용되지만,

이 때까지만 해도 이런 종류의 기기는 전문가 집단만 사용하고 있었죠.


다 제껴두고 아래 소개 동양상 보고 홀딱 반해서 투자하게 됩니다. 75 USD...


* Gimbal for your Lights Camera or Action

https://www.kickstarter.com/projects/2035152529/gimbal-for-your-lights-camera-or-action/description



일단 동영상을 함 봐보세요. 2015년 감각으로... 사고싶어 지죠?!


당시에는, 아무 생각 없이 투자한 것이라, 어떻게 활용할지, 어떻게 컨트롤 할지 상상도 못하고 있었습니다.

제품이 도착 후, servo 로 움직인다는 것을 알고 (도착해서야!) arduino 를 활용해 기본적인 동작만 확인하고 쳐박에 두었습니다.

왜냐 하면, arduino 에 대한 지식이 얕았기에 뭘 더 할 수가 없었거든요.


아래는 지금까지 gimbal 과 관련된 포스트 들 입니다.


* Hardware | Arduino 로 Servo 를 움직여 보자

https://chocoball.tistory.com/entry/Hardware-Arduino-Servo


* Hardware | Arduino 의 Sensor Shield 사용해 보기

https://chocoball.tistory.com/entry/Hardware-Arduino-Sensor-Shield


* Hardware | Dual-axis XY Joystick Module

https://chocoball.tistory.com/entry/Hardware-dual-axis-XY-Joystick-Module


마지막으로 Joystick 모듈을 사용해 보고서, 드디어! gimbal 활용의 완성판을 작성할 수 있을것 같아, 이 글을 쓰기 시작했습니다.





2. 연결


최종 목적은 Joystick 을 가지고, Gimbal 을 상하좌우 움직이는 것 입니다.

이게 단순하지만, Joystick 의 운동 한계성으로 그리 쉽게는 되지 않더군요.


일단, Arduino > Joystick > Gimbal (servo) 를 아래와 같이 연결합니다.


  Joystick | Arduino Nano
--------------------------
    GND    |     GND
    5V     |     5V
    VRX    |     A0
    VRY    |     A1
    SW     |     D2
--------------------------
  Servo 1  |
--------------------------
    RED    |     5V
    BLACK  |     GND
    SIGNAL |     D9
--------------------------
  Servo 2  |
--------------------------
    RED    |     5V
    BLACK  |     GND
    SIGNAL |     D8
--------------------------


실제 diagram 은 다음과 같습니다.



실제 코드에서는 Joystick 의 SW_pin 이 D2 에 연결되는 등, 위의 그림과 조금 다릅니다만, 대략적인 연결 구조만 보시면 되겠습니다.





3. 간단 구동


일단 Joystick 이 유효한 방법인지 알아볼 수 있는 간단한 코드 입니다.

인터넷 어디에선가 참조했는데 링크를 까먹었습니다. 간단한 연결법이라 검색해서 쉽게 찾을 수 있습니다.


#include "Servo.h"

Servo myservo1; // create servo object to control a servo
Servo myservo2;
int potpin1 = A0; // analog pin used to connect the potentiometer
int potpin2 = A1;
int val; // variable to read the value from the analog pin

void setup() {
	myservo1.attach(9);
	myservo2.attach(10); // attaches the servo on pin 9 to the servo object
}

void loop() {
	val = analogRead(float(potpin1)); // reads the value of the potentiometer (value between 0 and 1023)
	val = map(val, 0, 1023, 0, 180); // scale it to use it with the servo (value between 0 and 180)
	myservo1.write(val); // sets the servo position according to the scaled value
	delay(15); // waits for the servo to get there
	
	val = analogRead(float(potpin2)); // reads the value of the potentiometer (value between 0 and 1023)
	val = map(val, 0, 1023, 0, 180); // scale it to use it with the servo (value between 0 and 180)
	myservo2.write(val); // sets the servo position according to the scaled value
	delay(15);
}


아래는 연결하고 실제 구동 영상입니다.



잘 돌아가쥬?

단, 여기서 문제점들이 몇가지 있습니다.


- ripple value

회로적인 문제로 ripple 값이 발생하여, 조이스틱을 움직이지 않았지만 덜덜 떨거나 깔끔한 움직임이 나오지 않습니다.

ripple 를 제거 해야 합니다.

- speed

돌아가는 속도가 너무 빨라, 원하는 방향으로 위치하기가 힘듭니다.

- available range

X 축의 경우, 90 돌아가지만, gimbal 은 360 가 돌아가게끔 되어 있습니다.

CCTV 등 에 사용된다고 가정하면, 필요 없는 각도나 회전은 발생하지 않게끔 해야 합니다.

- last position

Joystick 의 성질 상, 움직힘 후 가운데로 되돌아 옵니다.

특정 위치를 잡게 하려면 동작시키지 않을 때에는 마지막 위치를 기억하고 있어야 합니다.

- centering

어떤 위치에서든 다시 초기 위치로 되돌아 오는 기능이 있으면 편할 것 같습니다.


이 글의 나머지 부분은, 결국 위의 문제를 해결하는 내용으로 진행됩니다.





4. Ripple Value


Joystick 을 가만히 놔둬도 계속 떨거나, 움직일 때 자연스럽지 못합니다.

Serial Monitor 로 값의 변화를 찍어 봤죠.



아무것도 안했는데도 값이 출렁 거립니다.

전후값을 비교해 보니, 가만히 있어도 최대 11 정도 값이 변하는 것을 알 수 있습니다.



그래프로 도식화 해보면, 그 현상을 충분히 알 수 있습니다.

이는 전기적인 ripple 일 수도 있습니다.


* How can I filter out noise from ADC lines without delay or signal change?

https://arduino.stackexchange.com/questions/994/how-can-i-filter-out-noise-from-adc-lines-without-delay-or-signal-change


  +5V                        +5V
   |                          |
(sensor)---resistor---+---(Arduino)
   |                  |       |
   |              capacitor   |
   |                  |       |
  GND----------------GND-----GND

이 경우 RC 필터를 넣으면 됩니다. 캐패시터 값으로 0.1uF capacitor 를 사용하면 적절하다고 하네요.


So if you are sampling at around 1000 Samples/second (i.e., a sampling interval of 1 millisecond), then 100 Ohms of resistance and (roughly) 100 uF ceramic capacitor may be adequate. (The RC time constant here is RC == 100 Ohms x 100 uF = 10 milliseconds).


다만, 저항이 들어가면서 값의 외곡이 생기니 software 적으로 처리하기로 합니다.


0              512            1023 
<---------------|--------------->
          497 |   | 527

그럼 어떻게 하느냐.

Joystick 에서 arduino 로 보내지는 전체 analog 신호 0~1023 에서,

정 가운데 앞뒤로 15씩 잘라, 이 구간의 ripple 이나 작은 움직임을 무시하기로 합니다.


...
		if ( (0 <= val) && (val < 497) ) {
			myservo1.write(angle1--);
		} else if ((527 <= val) && (val < 1024)) {
			myservo1.write(angle1++);
...


위의 코드에서 보면, 중간값을 잘라 낸 앞뒤 값에서 val 을 읽어 들이는 것으로 했습니다.




5. Speed & Last Poision


원래 소스에서는 최종 위치로 바로 움직이도록 값을 넣어 주지만, 수정된 소스에서는 각도를 1도씩 움직이게 했습니다.

그러면서, delay 값을 낮춘 채로 유지했습니다.


...
		if ( (0 <= val) && (val < 497) ) {
			myservo1.write(angle1--);
		} else if ((527 <= val) && (val < 1024)) {
			myservo1.write(angle1++);
		} else {
			// do nothing
		}
...
	delay(10);
...
		if ( (0 <= val) && (val < 497) ) {
			myservo2.write(angle2--);
		} else if ((527 <= val) && (val < 1024)) {
			myservo2.write(angle2++);
		} else {
			// do nothing
		}
...
	delay(10);  
}


속도에 변화를 더 주고 싶으면, angle 은 1도씩 움직이는 것은 놔두고, delay 를 조정 합니다.

저는 delay(10) 이 가장 적절했습니다.


또한, 최종 각도를 입력하는 것이 아닌, 한쪽 방향으로 움직임만 있으면 (exist) 1도씩 움직이게 한 결과,

자연스럽게 마지막 위치에서 멈춰있게 되었습니다.




6. Available Range


180도로 움직이는 Servo 를 풀로 사용하면, 카메라를 달았을 때, 촬영 범위를 벗어나거나 회전하게 됩니다.


일단 Y 축 servo 를 생각하면,

천장과 땅바닥을 촬영 각도에서 빼게 된다면, 전체 180 도에서 140도로 좁히면 됩니다.

그렇다면, 20 > 160 도 사이를 움직이게 코딩해 줍니다.


X 축은 180 회전이 720도 회전, 90 회전이 360도, 45 회전이 180도, 22.5 회전이 되어야 비로서 90도가 됩니다.

즉, 좌우로 22.5 를 먹여야 원하는 좌우 180도로 움직이는 결과가 되지요.


...
	if ( (67 < angle1) && ( angle1 < 112) ) { // making available range
...
	} else if ( angle1 == 67) {
		angle1++;
	} else if ( angle1 == 112) {
		angle1--;
	}
...
	if ( (20 < angle2) && ( angle2 < 160) ) { // making available range
...
	} else if ( angle2 == 20) {
		angle2++;
	} else if ( angle2 == 160) {
		angle2--;
	}
...


코딩에서는 90도가 center 이므로, 이 중간을 기준으로 좌우 +/- 한 값으로 범위를 정의했습니다.

위의 소스 마지막 부분에서, 각도의 끝에 있는 값에서 +1 / -1 씩 해준 이유는, 계속 범위 안에 들기 하기 위함입니다.

이게 없으면, 1도씩 움직이던 angle 값이, 범위 밖으로 나가면서 예외상황에 빠지게 됩니다.


코딩 해보신 분이라면, 예외사항 처리가 얼마나 중요한지 아실꺼예요.




7. Centering


Joystick 에서 SW_pin 이 존재합니다.

유일하게 digital 입력이며, 조이스틱을 누르면 딸깍 하는 소래를 내는 그 부분 입니다.


마침 잘 된 거죠. Centering 을 이 SW_pin 을 사용하면 됩니다.


#include "Servo.h"

const int SW_pin = 2; // digital pin for centering
...
void setup() {
	pinMode(SW_pin, INPUT);
	digitalWrite(SW_pin, HIGH);
...
}

void loop() {
	
	if (!digitalRead(SW_pin)) {
		angle1 = 90;
		angle2 = 90;
		myservo1.write(angle1);
		myservo2.write(angle2 );
	}
...
}


SW_pin 에 입력이 있으면, 중앙이 되는 90 도를 강제적으로 input 하게 하면 됩니다.




8. 정리


지금까지 해결했던 문제들을 한데 모아 소스코드로 만들면 아래와 같이 됩니다.


#include "Servo.h"

const int SW_pin = 2; // digital pin for centering
Servo myservo1; // create servo object to control a servo
Servo myservo2;
int potpin1 = A0; // analog pin used to connect the potentiometer
int potpin2 = A1;
int val; // variable to read the value from the analog pin
int angle1 = 90;
int angle2 = 90;

void setup() {
	pinMode(SW_pin, INPUT);
	digitalWrite(SW_pin, HIGH);
	myservo1.attach(9);
	myservo2.attach(10); // attaches the servo on pin 9 to the servo object
	
	myservo1.write(angle1);
	myservo2.write(angle2);
	delay (1000); // initalizing
}

void loop() {
	
	if (!digitalRead(SW_pin)) {
		angle1 = 90;
		angle2 = 90;
		myservo1.write(angle1);
		myservo2.write(angle2 );
	}
	
	val = analogRead(float(potpin1)); // reads the value of the potentiometer (value between 0 and 1023)
	if ( (67 < angle1) && ( angle1 < 112) ) { // making available range
		if ( (0 <= val) && (val < 497) ) {
			myservo1.write(angle1--);
		} else if ((527 <= val) && (val < 1024)) {
			myservo1.write(angle1++);
		} else {
			// do nothing
		}
	} else if ( angle1 == 67) {
		angle1++;
	} else if ( angle1 == 112) {
		angle1--;
	}
	delay(10);
	
	val = analogRead(float(potpin2)); // reads the value of the potentiometer (value between 0 and 1023)
	if ( (20 < angle2) && ( angle2 < 160) ) { // making available range
		if ( (0 <= val) && (val < 497) ) {
			myservo2.write(angle2--);
		} else if ((527 <= val) && (val < 1024)) {
			myservo2.write(angle2++);
		} else {
			// do nothing
		}
	} else if ( angle2 == 20) {
		angle2++;
	} else if ( angle2 == 160) {
		angle2--;
	}
	delay(10);  
}


코드의 최적화는 생각하지 않고, 오로지 구현에만 신경쓴 소스입니다.

모든 구현이 들어간 코드를 입힌 동영상 입니다.



원하는 수준까지 동작 구현이 되어서 기분이 좋네요.

향후, ESP8266 을 이용하여 원격 조정이나 Web UI 를 통한 컨트롤도 가능하겠습니다.


모두 happy arudino~!


And