제품 촬영을 위한 Turtle Table 2/3
터틀 테이블에 대한 두 번째 글입니다. 이전 포스트에서 제품 디자인 및 사용할 부품들을 소개했고, 이번 글에서는 각 부품의 연결 방법 및 관련된 코딩에 대해 설명하겠습니다.
전체적인 회로 구성은 위와 같습니다. USB를 통해 입력 받은 전원을 아두이노, 서보 모터, 로터리 엔코더에 공급하고, 각 부품은 아두이노의 D9 ~ D6핀에 차례로 연결합니다.
자세한 핀 연결은 아래와 같습니다.
- D9 : 서보 모터와 연결하여 회전 방향 및 속도를 제어합니다.
- D8 : 로터리 엔코더의 클럭(CLK) 핀과 연결합니다.
- D7 : 로터리 엔코더의 데이터(DT) 핀과 연결합니다.
- D6 : 로터리 엔코더의 스위치(SW) 핀과 연결합니다.
이전 글에서도 언급했지만 보드 자체에 대한 설명은 아래 이전 포스트를 참고하시면 됩니다.
이 보드는 아쉽게도 고정을 위한 볼트 홀이 없습니다. 그래서 케이스에 고정하기 위해 아래의 PCB를 사용하였습니다.
이번에 사용한 기판입니다. Adafruit사 제품으로 브레드보드와 동일하게 배선 처리되어 있는 것을 확인할 수 있습니다. 납땜해야 하는 것을 빼면 브레드보드와 사용법이 동일하고 고정을 위한 볼트 홀도 있어서 쉽게 프로젝트에 적용할 수 있습니다. Adafruit 홈페이지에서 “PERMA-PROTOS”라는 이름으로 판매하고 있고 사이즈도 여러 가지 이며 라즈베리파이를 위한 제품도 있지만 막 쓰기에는 가격이 좀 비싼 편입니다. 자세한 정보는 아래 링크를 확인하세요.
위와 같이 소켓을 납땜하여 아두이노를 삽입하였습니다. 입출력 및 전원을 위한 핀도 준비하였는데, 원래 다른 곳에 사용하려고 만들었던 것이라 핀 소켓을 꽉 채워서 납땜했습니다. 이번 프로젝트에선 단지 몇 개의 핀만 사용하기 때문에 너무 과한 준비가 되었네요.
연결할 곳은 위와 같습니다. 파워는 USB포트를 통해 5V를 공급 받아 아두이노, 엔코더, 모터에 제공합니다. 초록색 부분이 아두이노에 전원을 공급하는 포트이고, 파란색 부분은 입출력과 관련된 포트입니다.
아두이노 프로 미니 보드와 펌웨어 업로드를 위한 USB to Serial 컨버터입니다. 역시 자세한 설명은 이전 포스트를 참고하세요!
마이크로 USB 포트를 사용하기 쉽도록 한 PCB 컨버터입니다. 전원 공급만이 목적이기 때문에 5핀 중 양쪽 끝에 있는 두 핀(VBUS, GND)만 사용합니다. 모터를 구동시켜야 하므로 충분한 전류를 공급할 수 있는 충전기와 연결해 줘야 하는데, 저는 2A 짜리 USB 충전기를 사용할 예정입니다.
5핀 헤더를 납땜하고 케이블도 준비했습니다. 사진상에는 "ㄱ"자형 핀 헤더이지만 공간이 안나와서 일자형으로 교체하였습니다.
위와 같이 연결하여 사용합니다. 볼트 구멍이 있어서 잦은 탈부착에도 떨어지지 않도록 단단히 고정할 수 있습니다.
테스트를 위해 아두이노에 연결하여 전원을 공급하였습니다. 보드의 LED를 통해 제대로 작동하는 것을 확인할 수 있습니다.
우선 위와 같이 3핀 커넥터를 제어선 1핀과 전원선 2핀으로 분리하였습니다.
모터를 기판에 연결하였습니다. 제어선은 소스에서 사용할 서보모터 라이브러리의 기본 예제와 동일하게 digital 9번 포트에 연결하였습니다. 이 모터의 동작 전압은 4.8 ~ 7.2V입니다. 저는 USB(5V)로 전원을 연결하기 때문에 풀 스피드로 구동되진 않지만, 이 프로젝트는 이 정도로 충분합니다.
여기서 사용하는 서보 모터는 이전 글에서 언급한 바와 같이 무한 회전 방식입니다. 일반 서보 모터와는 달리 회전량(각도) 조절이 의미가 없고 지시하는 방향으로 항상 회전만 합니다. 그래서 프로그램 소스도 아주 간단하게 구현할 수 있습니다.
#include <Servo.h>
우선 서보 모터를 구동하기 위한 라이브러리를 포함시킵니다. 기본 라이브러리이므로 따로 설치할 필요는 없습니다.
#include <Servo.h>
Servo myservo; // create servo object to control a servo
그 다음 서보 모터의 오브젝트(object)를 생성합니다. 이 오브젝트(myservo)를 통해 모터를 제어하게 됩니다.
#include <Servo.h>
Servo myservo; // create servo object to control a servo
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
서보 모터가 D9번 포트에 연결되었음을 알려주는 명령입니다. 프로그램 내에서 한번만 수행하면 되기 때문에 setup() 함수에 코딩합니다.
myservo.write(value);
마지막으로 이 명령만 알면 끝입니다. write() 함수는 일반 서보 모터일 때 0에서 180까지의 각도를 조절하는 명령이며 인수 value는 마찬가지로 0에서 180까지의 값을 가집니다. 이와는 달리 무한 회전 서보에선 같은 값으로 모터의 회전 방향과 스피드를 제어합니다.
myservo.write(90); // stop
myservo.write(0); // clockwise(full speed)
myservo.write(180); // counterclockwise(full speed)
우선 0에서 180까지의 범위에서 중간 값인 90은 속도 “0” 즉 정지를 의미합니다. 인수 value가 “0”값이면 제가 가진 모터의 경우 시계 방향으로 최고 속도로 무한 회전하고, 값이 “180”이면 반 시계 방향으로 역시 최고 속도로 회전합니다. 정지 값인 90에서 0이나 180을 향하여 감소 또는 증가하면 그에 따라 회전 속도도 증가합니다. 즉, 숫자 89는 시계 방향으로 가장 느린 회전이고 88, 87, 86 등으로 숫자가 감소하여 "0"에 가까워질수록 속도가 빨라지고, 반대로 숫자 91은 반 시계 방향으로 가장 느린 회전을 의미하며 역시 180에 가까워질수록 빨라집니다.
그러나 제가 가진 모터만 그런 건지 모르지만 속도 변화가 그리 민감하진 않습니다. 숫자 1, 2 정도의 변화는 표가 나지 않고, 조금만 높아져도 바로 풀 스피드라서 거의 의미가 없었습니다. 시계 방향의 경우 4에서 구동을 시작하고 7정도면 이미 최고 속도입니다. 반 시계 방향의 경우 7은 돼야 구동되고 10 정도면 역시 풀 스피드입니다. 물론, 세밀한 속도 조절이 필요 없는 터틀 테이블의 경우 조금만 돌려도 풀 스피드가 나오는 점이 괜찮을 수도 있겠네요.
write() 함수로 구동 명령을 내리면 다시 write() 함수를 호출하기 전까지 동작을 계속합니다.
#include <Servo.h>
//
Servo myservo; // create servo object to control a servo
//
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
//
void loop() {
myservo.write(0); // clockwise full speed
delay(3000);
myservo.write(90); // stop
delay(1000);
myservo.write(180); // counterclockwise full speed
delay(3000);
myservo.write(90); // stop
delay(1000);
}
모터를 이용한 간단한 예제와 구동 영상입니다. 시계 방향(3초), 정지(1초), 반 시계 방향(3초), 정지(1초)의 네 가지 동작을 계속 반복합니다.
#include <Servo.h>
//
Servo myservo; // create servo object to control a servo
//
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
//
void loop() {
myservo.write(85);
delay(3000);
myservo.write(80);
delay(3000);
myservo.write(75);
delay(3000);
myservo.write(70);
delay(3000);
myservo.write(65);
delay(3000);
myservo.write(0);
delay(3000);
myservo.write(90);
delay(1000);
}
두 번째 예제입니다. 시계 방향으로 몇 단계 속도 변화를 주었는데, 85 정도에서 약간 느릴 뿐, 80에서 이미 최고 속도입니다.
로터리 엔코더는 이름 그대로 회전량을 디지털 코드(이진수)로 변환합니다. 엔코더의 축(shaft)을 돌릴 때 CLK, DT 포트로 펄스를 출력하는데 이 두 개의 펄스를 이용해 회전량과 회전 방향을 구할 수 있습니다. 자세한 내용은 나중에 따로 글을 작성하여 설명하고 여기서는 코딩 방법만 간단하게 언급하겠습니다.
여기서 사용할 엔코더는 PCB에 엔코더와 저항 그리고 입출력용 핀 헤더 등을 추가하여, 사용하기 쉽게 구성한 Rotary encoder Break out board입니다.
출력 핀 3개(CLK, DT, SW)는 아두이노의 D8, D7, D6핀에 차례로 연결하고 전원은 아두이노처럼 USB를 통해 입력 받으며 연결 케이블도 여기에 맞게 3핀, 2핀으로 나누어 준비했습니다.
이제 엔코더를 위한 소스 코드를 알아보겠습니다.
#define sw 6
#define dt 7
#define clk 8
라이브러리는 따로 필요 없고 먼저 사용할 입출력 핀에 대한 상수를 선언합니다. 그냥 숫자로 구분하는 것보다 알아보기 쉽게 하기 위함입니다. 또, 다른 핀으로 변경할 때도 이 곳 한군데만 바꾸면 되니까 편하구요. 만약, 엔코더 입력에 민감하게 반응해야 한다면 인터럽트 방식으로 연결하는게 더 좋을 겁니다.
pinMode(clk, INPUT);
우선 CLK 포트의 입력 값만 체크해 보겠습니다. 위와 같이 setup() 함수내에 clk(D8핀)의 핀모드를 입력 모드로 설정합니다.
int clkValue = digitalRead(clk);
이제 해당 핀으로 입력 값을 읽어서 저장합니다. 일반적으로 디지털 포트를 통해 입력 받는 코드와 다를 바 없으며, clkValue 변수에 저장되는 값은 항상 0 아니면 1입니다.
#define sw 6
#define dt 7
#define clk 8
//
void setup() {
Serial.begin(9600);
pinMode(clk, INPUT);
}
//
void loop() {
int clkValue = digitalRead(clk);
Serial.println(clkValue);
}
CLK 값을 체크하는 간단한 예제와 그 결과입니다. loop() 함수 내에서 clk 값을 입력 받아 시리얼 모니터에 출력하고 이를 계속 반복하기 때문에 엔코더의 입력이 있든 없든 항상 “0” 이나 “1”값을 출력합니다. “1”은 입력이 없을 때이고, “0”은 엔코더를 돌렸을 때에 해당합니다. 보통 “1”값이 입력이 발생했음을 의미하는 것과는 반대인데, 엔코더 자체는 입력이 발생하면 “1”을 출력하지만 풀-업 레지스터(pull-up register)에 의해 값이 반전됩니다.
엔코더 브레이크아웃 보드 아래쪽을 보면 저항이 납땜되어 있습니다. R2, R3는 CLK, DT와 연결되며 5V에 Pull-Up 되어 있습니다. 풀-업 레지스터를 사용하는 이유는 입력으로부터 잡음을 제거하기 위함이고 풀-다운이 아닌 풀-업이기 때문에 입력이 없을 때는 항상 “1”을, 입력이 발생하면 “0”을 출력합니다.
그런데, 푸시 스위치를 위한 R1 자리는 저항이 없습니다. 단가를 줄이기 위함인지 풀-업 되어 있지 않으므로 따로 처리해줘야 합니다.
#define sw 6
#define dt 7
#define clk 8
//
int clkValue = 0;
//
void setup() {
pinMode(clk, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
//
void loop() {
clkValue = digitalRead(clk);
if (clkValue == HIGH) {
digitalWrite(LED_BUILTIN, LOW);
} else {
digitalWrite(LED_BUILTIN, HIGH);
}
}
엔코더의 CLK 입력 값을 체크하는 두 번째 예제와 결과 영상입니다. 이전 예제에서 시리얼 모니터와 관련된 부분은 빼고, 입력 값이 있을 때만 내장 LED가 켜지도록 코딩한 것입니다. “LED_BUILTIN”은 내장 LED(온-보드, 빌트-인)를 의미하며 여기서는 숫자 “13”으로 변경해도 동일합니다. 위에서 설명했듯이 clk를 읽어 저장한 clkValue 변수의 값은 입력이 없으면 “HIGH”이고 이 경우 LED를 꺼주며 아니라면 입력이 있을 때의 “LOW”이므로 LED를 켜줍니다.
엔코더와 관련된 코딩에 대한 설명이 좀 많기 때문에 이번 포스트는 여기서 줄이고 다음 글에서 계속하고 완성까지 하겠습니다. 이상입니다.