본문 바로가기

천체관측(코동)

코스트코 천체망원경(코동) DIY 전동 포커서 간소화

코동(코스트코 동생 망원경 - 최초의 망원경은 반사망원경?)은 가성비의 NEXSTAR 90GT 굴절식 천체 망원경을 가리키는 말입니다. 지금은 중고로밖에 구할 수 없습니다. 

 

왜 전동 포커서를 DIY하게 되었나?

  • 보통 추운 날씨에 관측을 하게 됩니다. 
  • 저는 기관지가 약해서 마스크를 꼭 쓰는데, 안경까지 쓰다보면 김이 서리다보면 초점 맞추기가 어려워집니다. 
  • 그리고 워낙 배율이 높은 상태로 망원경을 조작하다보면 조금만 건드려도 대상물이 시야에서 사라집니다. 
  • 초점을 맞추려면 망원경을 건드리지 않을 수가 없겠죠? 
  • 그래서, 전동포커서가 있으면 좋겠다는 생각을 하게 됩니다. 

물론, 항상 선배님들이 존재하고 찾아보면 자작한 분도 있고, 공동구매한 분들도 있는데 지금와서 구하기는 어려워 보였습니다. 

 

[1차 프로토타입]

 

 


1) 모터 

- 생각보다 10rpm 모터 찾기가 어려웠습니다. 결국 아마존에서 Greartisan 12V 10rpm 모터를 구입했습니다. Aliexpress에서도 하나 샀지만, 설이 끼면서 배송도 너무 느렸고 빠르게 도착한 아마존 모터가 워낙 튼튼하고 강력해서 따로 테스트는 안 했습니다. 
- 10rpm을 고른 이유는 선배님들의 가르침으로,,, 참고: https://m.blog.naver.com/taifight/220801984510 위에 링크한 동작 영상을 보시다시피, 10rpm정도면 적당하다는 느낌이 듭니다.  

2) 타이밍 벨트 

- 모터 찾기만큼 어려웠습니다. 
- https://www.aliexpress.com/item/33005589716.html?spm=a2g0o.order_list.0.0.21ef18020OZmtK 에서 6mm-20T, 10mm-40T를 선택했는데, 코동의 초점 휠을 빼낸 후 재보면 8mm가 조금 넘는 정도이지만 10mm로 구입했습니다. 대신 부족한 부분은 뭔가로 메꾸어야겠지요. 

3) 아두이노 프로 미니 3.3V

- 아두이노 블루투스 모듈 HC-06과 통신할 때를 위해 3.3V로 사야 편안합니다. 5V로 사면 레벨컨버터나 저항을 달아야 할 거예요.

4) 아두이노 블루투스 모듈 HC-06 

- BLE 버전도 있지만, 오래되어도 단순 무식한 것이 간단합니다. Arduino bluetooth controller앱을 Play 스토어에서 받고 나서 조이스틱 버튼마다 명령어 글자를 매핑해 줍니다. 
- 좌측: L, 상향: U, 우측: R, 하향: D
- 좌우측 방향은 1초간, 위아래 방향은 4초간 모터를 구동해 보았습니다.   

5) L298N 모터 드라이버 

- 12V를 넣고 모터 제어 12V와 아두이노 프로 미니 구동용 5V전원을 공급해 줍니다. 구입한 아두이노 프로 미니는 3.3V버전이므로 VCC에 연결하면 안되고, 5V선은 RAW핀에 연결해야겠습니다. 

6) USB to 12V 레벨업 컨버터 700mA

- 20000mA짜리 외장 배터리로부터 12V를 공급하면 되겠습니다. 

7) 아두이노 모터 구동 테스트 코드

#include <SoftwareSerial.h>

int motor_1 = 10;
int motor_2 = 9;
int motor_latest = 0; 

SoftwareSerial BTSerial(2,3); 

void setup() {
  // put your setup code here, to run once:
  pinMode(motor_1, OUTPUT);
  pinMode(motor_2, OUTPUT);

  Serial.begin(9600); 
  BTSerial.begin(9600); 
}

void loop() {
  // put your main code here, to run repeatedly:
  byte ch; 
  
  if (BTSerial.available()) {
    ch = BTSerial.read();     
    if (ch == 'L' || ch == 'U') {  
      analogWrite(motor_1, 255);
      digitalWrite(motor_2, 0);
      motor_latest = 1; 
    } else if(ch == 'R' || ch == 'D') {
      digitalWrite(motor_1, 0);
      analogWrite(motor_2, 128);
      motor_latest = 2; 
    }
  }

  if (motor_latest == 1){
    delay(1000); 
    if (ch == 'U') delay(4000); 
    analogWrite(motor_1, 0); 
    motor_latest = 0; 
  } else if (motor_latest == 2){
    delay(1000); 
    if (ch == 'D') delay(4000); 
    analogWrite(motor_2, 0);
    motor_latest = 0; 
  }
}

 

[2차 프로토타입] 

종이상자로 쓰기는 곤란하고 3D 프린팅을 하거나 오토캐드로 설계해서 제작해야 하는데,,, 일단 몇 천원으로 막아보았습니다. 

 

 
받침대 업그레이드에 사용한 재료는 다음과 같습니다. 

 

  • CCTV 각도기 - 높이 50mm짜리인데 사실 49mm정도가 필요하여 나사를 풀고 조금 기울여서 사용합니다.   
  • 케이블 타이 긴 것 - 경통에 각도기를 고정하는 용도로 사용 
  • 투명 양면 매직 접착 테이프 - 두께 2mm 정도로 모터와 각도기, 각도기와 망원경 경통 사이에 미끄러지지 않는 용도로 사용, PCB 보드 고정용으로 사용 
그리고, 지난번에는 Arduino bluetooth controller 앱을 써서 경통 길이를 제어했는데, 어차피 라즈베리파이를 달아서 경위도가대를 움직이고 RPi 카메라를 원격으로 보고 있으므로, 스마트폰을 쓰지 않고 라즈베리파이와 HC-06 블루투스 모듈을 연결하여 제어해 보았습니다. 이제 스마트폰도 필요 없으니 정말로 안방에 앉아서 옥상의 코동을 완전히 제어하게 되는 것입니다. 
라즈베리파이와 HC-06의 연결은 다음 글을 참고했어요. - https://lifeonroom.com/diy/raspberry-pi-3-bluetooth-client/
  • 일단 페어링이 되면, 망원경 경통 제어를 위해 L, R, U, D의 4가지 글자를 보내면 되므로, 위 링크에 있는 Python 프로그램을 그대로 이용해도 됩니다.
  • 키보드로 대문자 치기 그러니까, 소켓으로 보낼 때 msg.upper()로 바꿔치기 하면 l, r, u, d를 타이핑해도 대문자로 바꾸어져서 나가겠지요. 
  • 또한, 경통을 많이 줌인/줌아웃하려면 여러 번 보내야 하는데, 이 때에는 LLLLLLL, RRRRRR 과 같이 여러 번 타이핑해서 Enter를 눌러도 됩니다. 다만, 모터가 회전할 때 힘이 세므로 최대 줌인, 줌아웃을 한 경우 코동 가대가 고장날 우려가 있으니 주의해야 됩니다. 현실에서는 원격으로는 미세 조정하는 정도라서 그럴 일은 없겠지만...

 

[3차 프로토타입]

 

기존의 전동포커서 장비는 

  • 아두이노 블루투스 모듈이 시리얼통신으로 값을 넘겨주면 아두이노 보드에서 L298N 모터드라이버를 통해 조절하는 방식이었습니다. 
  • 스마트폰이나 라즈베리파이에서 블루투스 모듈로 통신하여 제어하는 것인데, 고장나서 잘 작동이 안되었습니다. 

이전 버전의 전동 포커서가 작동하는 모습은 다음과 같습니다. 사실 이번에는 적외선 리모콘으로 제어하게 되었고, 연결된 회로만 더 단순화되었을 뿐 작동 메카니즘은 동일합니다. 

https://youtu.be/01qGDRIuWgQ

 

새로운 전동포커서 장비는 

  • 라즈베리파이에 적외선 리모콘 수신부를 추가하고, 
  • 리모콘으로 제어하면 라즈베리파이의 GPIO를 이용하여 L298N 모터 드라이버를 직접 제어하는 것입니다. 
  • 이로써 아두이노 보드와 아두이노 블루투스 모듈을 없앨 수 있어서 간단해졌고, 별도의 적외선 리모콘으로 편리하게 제어할 수 있게 되었습니다. 

 

적외선리모콘, 리모콘수신부, 라즈베리파이4(Astroberry 설치)

 

작업하면서 참조한 글들은 다음과 같습니다. 1번이 의외로 가장 난관이었습니다. 2번도 어려울 뻔 했지만 digikey.kr에서 정리해 준 글을 보고 바로 작동이 되었습니다. 

  1. 라즈베리파이에서 리모콘을 설정 - 라즈베리파이 LIRC(리모컨 송수신) 설정 #2 – nakwonelec 참조
    • 리모콘 키를 돌아가면서 하나씩 입력을 하는데 입력할 때마다 . 이 표시가 됩니다. 이게 너무 길게 눌러도 안되고 너무 짧게 눌러도 안된다는 설명이 있는데 대충 하나~둘~셋 한 후 다음 키를 연달아 누르면 됩니다. 문제는 첫 줄 입력 후 Got Gap이 어쩌구 하면서 오류처럼 보이는데, 여기에 굴하지 말고 영어로 표시되는 메시지를 잘 보면서 또 한번 리모콘 키를 골고루 바꿔가면서 같은 패턴으로 눌러 주다보면, 드디어 키에 이름을 하나씩 지정한 후 입력하게 되는 단계에 이르릅니다.
    • 위 링크의 글을 보기 전에 개인적으로는 LIRC: irrecord wont record, (Buster), mode2 works - Raspberry Pi Stack Exchange 라는 글을 보고, Answer에 있는 패치도 (버전이 달라서 수동으로) 파일을 편집해서 해봤지만 별 차이는 없었습니다. 
    • 또 한가지, 출처는 기록을 안했지만 1번에서 만든 conf파일에 다음 2개의 줄을 추가해 주었습니다.(저의 경우에는 /etc/lirc/lircd.conf.d/yurobot.lircd.conf에 아래 내용을 추가)
      - min_repeat   1
      - suppress_repeat 2
  2. Python으로 리모콘 값 읽기 - How to Send and Receive IR Signals with a Raspberry Pi (digikey.kr) 참조 
  3. Python으로 L298N 모터 드라이버 제어하기 - 7. 라즈베리파이 DC 모터 제어하기 1 (tistory.com) 참조 

결과적으로 위 2번의 소스를 바탕으로 3번의 소스를 대충 조합하면 원하는 동작을 얻을 수 있었습니다. 

 

# This script is based on the following script: https://github.com/akkana/scripts/blob/master/rpi/pyirw.py
# It opens a socket connection to the lirc daemon and parses the commands that the daemon receives
# It then checks whether a specific command was received and generates output accordingly

import os
import socket
from gpiozero import Motor
import time 
import queue 
import threading
import serial 
from tendo import singleton

me = singleton.SingleInstance() 

SOCKPATH = "/var/run/lirc/lircd"
sock = None

motor = Motor(forward=21, backward=20)

port = "/dev/ttyUSB0"
baud = 9600

queueKey = queue.Queue() 

# Establish a socket connection to the lirc daemon
def init_irw():
    global sock
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect(SOCKPATH)

    print ("Press 'E' of IrDA remocon or Ctrl-C to exit program.")
    print ("Press 'C' for shutdown of RPi, Press 'D' to reboot RPi.")
    print ("To adjust rotation rate, use '0'(down to 2) and 'F'(up to 7) key.")

# parse the output from the daemon socket
def getKey():    
    while True:
        data = sock.recv(128)
        data = data.strip()

        if (len(data) > 0):
            words = data.split()
            queueKey.put(words)

def readthread(ser): 
    while True: 
        if (ser.inWaiting() > 0):
            data_str = ser.read(ser.inWaiting()).decode('ascii') 
            # print(data_str, end='')
        
def sendslewcommand(ser, azm, positive, rate): 
    if azm: 
        if positive: 
            data = chr(80) + chr(2) + chr(16) + chr(36) + chr(rate) + chr(0) + chr(0) + chr(0)
        else:
            data = chr(80) + chr(2) + chr(16) + chr(37) + chr(rate) + chr(0) + chr(0) + chr(0)
    else: 
        if positive: 
            data = chr(80) + chr(2) + chr(17) + chr(36) + chr(rate) + chr(0) + chr(0) + chr(0)
        else:
            data = chr(80) + chr(2) + chr(17) + chr(37) + chr(rate) + chr(0) + chr(0) + chr(0)
    
    data = bytes(data,'ascii')
    ser.write(data)
    time.sleep(0.1)     

def controlNexstar(event):  
    rate = 7
    last_azm = True 
    
    while True:
        words = queueKey.get()
        key = words[2].decode()     
        repeat = words[1].decode()       

        if (key == "KEY_E"):
            break
        elif (key == "KEY_D"):
            os.system('sudo reboot')
        elif (key == "KEY_C"): 
            os.system('sudo shutdown -h now')
        elif (key == "KEY_LEFT"):
            motor.forward() 
            event.wait(0.05)
            motor.stop()
        elif (key == "KEY_RIGHT"):
            motor.backward() 
            event.wait(0.05)
            motor.stop()
        elif (key == "KEY_UP"):
            motor.forward() 
        elif (key == "KEY_DOWN"):
            motor.backward() 
        elif (key == "KEY_OK"): 
            motor.stop()
        elif (key == "KEY_NUMERIC_4"): 
            sendslewcommand(ser, True, False, rate)
            last_azm = True 
        elif (key == "KEY_NUMERIC_6"): 
            sendslewcommand(ser, True, True, rate)
            last_azm = True 
        elif (key == "KEY_NUMERIC_2"): 
            sendslewcommand(ser, False, True, rate)
            last_azm = False 
        elif (key == "KEY_NUMERIC_8"): 
            sendslewcommand(ser, False, False, rate)
            last_azm = False 
        elif (key == "KEY_NUMERIC_5"):   
            if last_azm: 
                sendslewcommand(ser, True, True, 0)
            else:
                sendslewcommand(ser, False, True, 0) 
            rate = 7
        elif (key == "KEY_NUMERIC_0"): 
            rate = rate - 1
            if (rate < 2): 
                rate = 2
        elif (key == "KEY_F"): 
            rate = rate + 1 
            if (rate > 7): 
                rate = 7

# Main entry point
# The try/except structures allows the users to exit out of the program
# with Ctrl + C. Doing so will close the socket gracefully.
if __name__ == '__main__':  
    try:
        ser = serial.Serial(port, baud, timeout=1)
        event = threading.Event()       

        init_irw()

        t1 = threading.Thread(target=getKey, daemon=True) 
        t1.start() 

        t2 = threading.Thread(target=controlNexstar, args=(event,))
        t2.start()
        
        t3 = threading.Thread(target=readthread, args=(ser,), daemon=True)
        t3.start()         
        
        t2.join()
    except KeyboardInterrupt:
        print ("\nShutting down...")
        # Close the socket (if it exists)
        if (sock != None):
            sock.close()
        if (ser != None): 
            ser.close() 
    except serial.serialutil.SerialException: 
        print ("No Nexstar connection found. Electonic focusser will work only.\n")

        event = threading.Event()

        init_irw()

        t1 = threading.Thread(target=getKey, daemon=True)
        t1.start()

        t2 = threading.Thread(target=controlNexstar, args=(event,))
        t2.start()        

        t2.join()
    finally:
        print ("pynexstarcontrol.py is done!\n")

 

 

E키를 누르면 종료하고, 왼쪽과 우측 키를 누르면 0.05초 동안 모터를 작동시키고, 위 아래 키를 누르면 0.1초 동안 모터를 작동시키게 됩니다. 그리고, 키를 계속 누르고 있으면 연속적으로 경통을 앞 뒤로 움직일 수 있습니다. 

 

사실 망원경을 밖에 두고 차 안이나 텐트 안에서 노트북 컴퓨터나 스마트폰을 통해서 감상하고, 적외선 리모콘으로 초점을 맞추는 것이 최종 그림입니다. 전에는 옥상에 두고도 이론적으로 전동 포커서를 작동시킬 수 있었지만, 사실 그럴 일은 별로 없었습니다. 더구나 경통의 끝을 인식하는 안전 기능이 없으므로 눈으로 보면서 조작해야만 합니다.