June 28, 2017

디바이스마트 미디어:

[38호]로꾸꺼 보트

2016 ictmain

2016  ICT 융합 프로젝트 공모전 참가상

로꾸꺼 보트

글 | 한국교통대학교 임성묵, 전동흡, 박재호, 오성석, 신민철

심사평

뉴티씨 해양 오염 등을 모니터링하거나, 원격으로 조정하여 보트를 통하여 수질개선을 시도하거나 현재의 상태를 보면서 대책을 강구할 수 있는 등 좋은 아이디어인데, 실제 구현까지는 아직 많은 단계가 남아있는 듯 하다. 이를 실제로 구현하여, 실제 사용 데모 등을 찍어서 함께 보여준다면 보다 좋은 작품이 될 것으로 생각하며, 많은 개선 여지를 남기고 있는 작품이다.

칩센 학생들이 도전하기 쉬운 주제지만 완성도가 조금 아쉽다.

위드로봇 보트를 위·아래 대칭 형태로 제작하는 아이디어가 무척 재미있다. 문제는 이렇게 제작한 보드가 실제 강이나 바다에서 동작하는데 문제가 없는지를 조사해 봐야 할 것 같은데, 그 부분에 대한 조사가 없는 것이 아쉽다.

작품개요
기존 무선 모터보트의 단점을 개선한다.

38 ict 로꾸꺼 (1)

① 보트의 전복을 방지
잔잔한 호숫가든 파도가 치는 바닷가든, 충돌의 가능성과 물살에 의해 전복이 되는 위험성이 있다.
: 일반적인 RC보트의 구조로는 파도가 조금 높고, 바람의 세기가 강해지면 쉽게 전복된다. 때문에 보트가 전복이 되더라도 상관없이 계속해서 정찰할 수 있도록 보트의 구조를 위·아래 대칭으로 만들었다. 보트가 전복되었을 때 카메라의 구도가 뒤집히고 좌우 모터의 위치가 바뀌기 때문에 자이로 센서의 값을 이용하여 컨트롤러의 방향에 맞게 보트의 앞·뒷면을 판단하고 카메라의 스트리밍 화면을 똑바로 볼 수 있게 화면을 조정해 준다.

38 ict 로꾸꺼 (2)

② 시야의 확보
보트가 시야에서 가려지면 컨트롤을 할 수 없다.
: 카메라를 통해서 시야를 확보했다. 카메라는 액션캠을 이용했다. 액션캠의 장점이 작고 가볍고 내구성이 좋으며 다각도를 촬영할 수 있다는 점 때문에 보트에 활용하기 적합하다. 그리고 움직이면서 촬영할 수 있기 때문에 일반 캠코더로는 불가능한 역동적인 동영상 촬영이 가능하다. 가장 큰 장점은 실시간 카메라 스트리밍을 이용하여 보트의 주변 환경정보를 받아들일 수 있으며 장거리에서도 주변 상황에 맞추어 보트를 작동하는 것이 가능하다.

③ 장거리 동작 가능
RC보트의 전파 도달거리는 약 50~60m정도로 그 범위를 벗어나게 되었을 경우 컨트롤로는 더 이상 조작을 할 수 없다.
: UDP통신을 이용하여 WIFI가 가능한 지역이라면 어디서든 원격으로 조정이 가능하다. 사람의 시야 밖에서도 카메라를 통해 주변 상황을 스트리밍 할 수 있기 때문에 원활한 조작이 가능하다. LTE를 이용하여 좀 더 원활한 조작을 하고 싶었지만 LTE 주파수는 기업에서 구매하여 사용하고 있기 때문에 개발하지 못하였다.

38 ict 로꾸꺼 (4)

④ 컨트롤방식의 다양성
기존의 RC보트의 컨트롤은 조이스틱만을 이용해 조작한다.
: 조이스틱을 이용한 조작과 더불어 자이로센서를 이용하여 컨트롤러의 기울기에 따른 조작을 할 수 있게 하여 컨트롤방식의 다양성을 부여하였다.

작품설명
주요동작 및 특징

38 ict 로꾸꺼 (5)

① 스위치

38 ict 로꾸꺼 (6)
1) 자이로센서로 보트가 동작한다. 조이스틱 모듈로는 동작하지 않는다.
2) 조이스틱 모듈로 보트가 동작한다. 자이로센서로는 동작하지 않는다.
※ 스위치를 위 1처럼 하기 전에 컨트롤러가 좌, 우, 앞으로 기울지 않은 상태에서 해야 한다.

②자이로센서

38 ict 로꾸꺼 (7)
컨트롤러를 번호의 방향대로 기울였을 때,
1) 보트가 제자리에서 왼쪽으로 돈다.
2) 보트가 제자리에서 오른쪽으로 돈다.
3) 보트가 앞으로 나아간다.
4) 보트가 앞으로 나아가며 컨트롤러를 기울인 방향으로 선회한다.

③ 조이스틱

38 ict 로꾸꺼 (8)
조이스틱을 다음 번호의 위치로 움직였을 때,
1) 보트가 제자리에서 왼쪽으로 돈다.
2) 보트가 제자리에서 오른쪽으로 돈다.
3) 보트가 앞으로 나아간다.
4) 보트가 앞으로 나아가며 조이스틱을 움직인 방향으로 선회한다.

전체 시스템 구성

38 ict 로꾸꺼 (1)
① 사용자 ▶ 보트
: 컨트롤러의 자이로 센서와 조이스틱 모듈에서 출력되는 값을 UDP 통신을 통해 보트의 라즈베리파이로 전송하여 보트의 움직임을 제어한다.
② 보트 ▶ 사용자
: 액션캠에서 받아들이는 영상정보를 라즈베리파이에서 MMAL 용 motion 프로그램을 사용한다. 사용자의 pc인 J-player로 전송하여 IP에 따라 실시간으로 받아들인다.

38 ict 로꾸꺼 (9)

① Brushless Motor
: ESC(Electric Speed Controller)를 통해 라즈베리파이의 GPIO핀에서 PWM 신호를 받아 컨트롤러에 따른 모터의 출력을 제어한다.
② 액션캠
: 라즈베리파이와의 USB연결을 통해 영상정보를 받아오고 라즈베리파이를 통해 UDP 통신으로 전송한다. 사용자의 PC에서 J-Player 화면으로 실시간으로 영상정보를 전송한다.
③ 자이로 센서
: I2C 통신을 이용하여 자이로 센서의 가속도 값을 라즈베리파이로 전송하여 보트의 상·하를 구별할 수 있도록 한다.

38 ict 로꾸꺼 (10)

① 무선 컨트롤러
: I2C 통신을 이용하여 자이로 센서의 자이로 가속도 값을 상보필터를 사용하여 X축과 Y축 값의 정보를 받아온다. 받아온 값을 UDP 통신을 통해 보트의 라즈베리파이로 전송한다. SPI 통신을 이용하여, 조이스틱의 방향에 따라 출력되는 아날로그 값을 디지털 값으로 변환하고 UDP 통신을 통해 보트의 라즈베리파이로 전송한다.
② 실시간 영상 수신
: 보트의 액션캠 영상정보를 사용자 PC에 J-Player를 이용하여 보트 라즈베리파이 IP를 통해 실시간으로 영상을 수신한다.

38 ict 로꾸꺼 (2)

· 컨트롤러의 스위치 값이 1이면 자이로 센서의 출력 값을 보트의 라즈베리파이로 전송한다.
· 컨트롤러의 스위치 값이 0이면 조이스틱의 방향에 따른 출력 값을 보트가 가지고 있는 모터제어 값에 맞게 변환하여 보트의 라즈베리파이로 전송한다.

38 ict 로꾸꺼 (3)

· 액션캠이 실행되어 실시간으로 영상정보전송을 시작한다.
· 자이로센서의 Z축 가속도 값을 수신하여 상·하를 구분한다.
· Z축 가속도 값에 따라 프로펠러의 포트를 선택한다.
· 컨트롤러로부터 받아오는 Roll/Pitch 값을 모터제어 함수를 거쳐, 조건에 따라 양측모터의 출력을 제어하여 보트를 동작시킨다.

개발환경
① 개발 보드
· 라즈베리파이
② 개발 환경
· 리눅스 기반의 Raspbian OS
③ 활용 프로그램
· J player
· Motion-MMAL(영상 전송을 위한 리눅스 프로그램)
④ 개발 언어
· python

단계별 제작과정

38 ict 로꾸꺼 (11)

① 보트의 외형을 기능이 잘 작동할 수 있도록 설계한다.

38 ict 로꾸꺼 (12)

② Sketch Up 프로그램을 이용하여 설계한 모형을 3D로 그려본다.

38 ict 로꾸꺼 (13)

③ 방수·단열재인 아이소핑크를 이용하여 보트의 외형을 제작한다.
· 크기에 맞게 아이소핑크를 자른다.
· ESC를 납땜한다.

38 ict 로꾸꺼 (14)

· 모터를 ESC와 연결하여 부착한다.
· ESC선을 내부로 집어넣는다.

38 ict 로꾸꺼 (15)

· 배터리/라즈베리파이/자이로센서/카메라를 내부에 부착한다.
· Glue-gun으로 모서리 방수처리를 한다.

38 ict 로꾸꺼 (16)

④ 컨트롤러를 제작한다.

38 ict 로꾸꺼 (17)

⑤ 컨트롤러와 보트에 코드를 작성한다.

38 ict 로꾸꺼 (18)

소스코드
① 컨트롤러

#필요한 라이브러리
import smbus
import time
import math
import string
import socket
import RPi.GPIO as GPIO
import os
import spidev

#ADC 변환위한 채널 설정
roll_ch = 1
pitch_ch = 2

#자이로, 가속도 센서 사용을 위해 정의 및 필요함수
power_mgmt_1 = 0x6b
power_mgmt_2 = 0x6c
gyro_scale = 131.0
accel_scale = 16384.0
address = 0×68
bus = smbus.SMBus(1)
bus.write_byte_data(address, power_mgmt_1, 0)

#UDP 통신 위한 선언
UDP_IP =”192.168.59.152”
UDP_PORT = 5005
sock1 = socket.socket(socket.AF_INET, #Internet
socket.SOCK_DGRAM) #UDP

#스위치 값 읽어오기 위한 선언
GPIO.setmode(GPIO.BCM)
GPIO.setup(23,GPIO.IN)

#조이스틱 모듈 값을 adc컨버터로 읽어오기 위한 선언 및 함수
spi = spidev.SpiDev()
spi.open(0,0)
def ReadChannel(channel):
adc = spi.xfer2([1,(8+channel)<<4,0])
data = ((adc[1]&3) << 8) + adc[2] return data

#UDP 통신을 위한 함수
def send(a,b):
for i,j in [(a,’a’),(b,’b’)]:
i = str(i)
buffer1 = j+i[0:] sock1.sendto(buffer1, (UDP_IP, UDP_PORT))

#자이로, 가속도 센서 사용을 위한 필요함수
def read_all():
raw_gyro_data = bus.read_i2c_block_data(address, 0×43, 6)
raw_accel_data = bus.read_i2c_block_data(address, 0x3b, 6)
gyro_scaled_x = twos_compliment((raw_gyro_data[0] << 8) +raw_gyro_data[1]) / gyro_scale
gyro_scaled_y = twos_compliment((raw_gyro_data[2] << 8) +raw_gyro_data[3]) / gyro_scale
gyro_scaled_z = twos_compliment((raw_gyro_data[4] << 8) +raw_gyro_data[5]) / gyro_scale

accel_scaled_x = twos_compliment((raw_accel_data[0] << 8) +raw_accel_data[1]) / accel_scale
accel_scaled_y = twos_compliment((raw_accel_data[2] << 8) +raw_accel_data[3]) / accel_scale
accel_scaled_z = twos_compliment((raw_accel_data[4] << 8) +raw_accel_data[5]) / accel_scale
return (gyro_scaled_x, gyro_scaled_y, gyro_scaled_z, accel_scaled_x, accel_scaled_y, accel_scaled_z)
def twos_compliment(val):
if (val >= 0×8000):
return -((65535 – val) + 1)
else:
return val
def dist(a, b):
return math.sqrt((a * a) + (b * b))
def get_y_rotation(x,y,z):
radians = math.atan2(x, dist(y,z))
return -math.degrees(radians)
def get_x_rotation(x,y,z):
radians = math.atan2(y, dist(x,z))
return math.degrees(radians)

#COMPLEMENTARY FILTER 함수
def com():
global last_com_x
global last_com_y
K = 0.98
K1 = 1 – K
time_diff = 0.001
(gyro_scaled_x, gyro_scaled_y, gyro_scaled_z, accel_scaled_x, accel_scaled_y, accel_scaled_z) = read_all()
last_1x = get_x_rotation(accel_scaled_x, accel_scaled_y, accel_scaled_z)
last_1y = get_y_rotation(accel_scaled_x, accel_scaled_y, accel_scaled_z)

gyro_offset_x = gyro_scaled_x
gyro_offset_y = gyro_scaled_y

gyro_total_x = (last_1x) – gyro_offset_x
gyro_total_y = (last_1y) – gyro_offset_y

(gyro_scaled_x, gyro_scaled_y, gyro_scaled_z, accel_scaled_x,
accel_scaled_y, accel_scaled_z) = read_all()

gyro_scaled_x -= gyro_offset_x
gyro_scaled_y -= gyro_offset_y

gyro_x_delta = (gyro_scaled_x * time_diff)
gyro_y_delta = (gyro_scaled_y * time_diff)

gyro_total_x += gyro_x_delta
gyro_total_y += gyro_y_delta

rotation_x = get_x_rotation(accel_scaled_x, accel_scaled_y, accel_scaled_z)
rotation_y = get_y_rotation(accel_scaled_x, accel_scaled_y, accel_scaled_z)
last_1x = K * (last_1x + gyro_x_delta) + (K1 * rotation_x)
last_1y = K * (last_1y + gyro_y_delta) + (K1 * rotation_y)

last_com_x=last_1x
last_com_y=last_1y

while 1:

#스위치 값이 0이라 조이스틱 값 전송
if GPIO.input(23)==0 :

#조이스틱 값 읽어오기
roll = ReadChannel(roll_ch)
pitch = ReadChannel(pitch_ch)

#알맞은 값으로 변환하기 위한 과정
if roll>510 and roll<530 :
roll = 520
elif roll>1020:
roll = 1020
elif roll<3:
roll = 3

roll_last = (((roll-520)-10)/16.3)
if roll_last<10 and roll_last>-10:
roll_last = 0
if roll_last>0 :
roll_last += 10
if roll_last<0 :
roll_last -= 10

if pitch>530 :
pitch = 520
elif pitch<3 :
pitch = 3

pitch_last = -(((pitch-520)-10)/16.3)
if pitch_last<5 :
pitch_last = 0
if pitch_last>10 :
pitch_last += 10

#알맞은 값으로 변환하기 위한 과정

#전송
send(roll_last,pitch_last)
print “roll:”,roll_last
print “pitch:”,pitch_last
time.sleep(0.3)

#스위치 값이 1이라 자이로, 가속도 값 전송
elif GPIO.input(23)==1 :

#COMPLEMENTARY FILTER 함수적용
com()
xro=last_com_x
yro=last_com_y

#전송
send(xro,yro)
print “xro:”,xro
print “yro:”,yro
time.sleep(0.3)

② 보트

#필요한 라이브러리
from Tkinter import*
import RPi.GPIO as GPIO
from RPIO import PWM
import time
import math
import string
import socket
import smbus

#UDP 통신을 위한 선언
UDP_IP =”192.168.59.152”
UDP_PORT = 5005
sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock1.bind((UDP_IP, UDP_PORT))
bus = smbus.SMBus(1)
address = 0×68
bus.write_byte_data(address, power_mgmt_1, 0)
#자이로, 가속도 센서 사용위해 정의 및 필요함수

#전송값 초기화
xro=0
yro=0

#자이로, 가속도 센서을 사용위해 정의 및 필요함수
power_mgmt_1 = 0x6b
power_mgmt_2 = 0x6c

def read_byte(adr):
return bus.read_byte_data(address, adr)
def read_word(adr):
high = bus.read_byte_data(address, adr)
low = bus.read_byte_data(address, adr+1)
val = (high << 8) + low
return val
def read_word_2c(adr):
val = read_word(adr)
if (val >= 0×8000):
return -((65535 – val) + 1)
else:
return val
def dist(a,b):
return math.sqrt((a*a)+(b*b))
def get_y_rotation(x,y,z):
radians = math.atan2(x, dist(y,z))
return -math.degrees(radians)
def get_x_rotation(x,y,z):
radians = math.atan2(y, dist(x,z))
return math.degrees(radians)

####모터 동작을 위한 함수####
#모터가 돌지 않는 경우
def STOP():
PWM.Servo().set_servo(motor_1,1000)
PWM.Servo().set_servo(motor_2,1000)

#방향 회전없이 앞으로만 가는 경우
def STRAIGHT(a) :
a=a//1
a=1400+(10*a)
PWM.Servo().set_servo(motor_1,a)
PWM.Servo().set_servo(motor_2,a)

#제자리 회전을 위한 경우
def S_TURN(a) :
if a==1:
PWM.Servo().set_servo(motor_1,1200)
PWM.Servo().set_servo(motor_2,1800)
elif a==2:
PWM.Servo().set_servo(motor_1,1800)
PWM.Servo().set_servo(motor_2,1200)

#앞으로 가면서 방향 회전을 하는 경우
def R_L_TURN(a,b) :
a=a//1
b=b//1
b=1400+(10*b)
if a<-10:
a=a*10
PWM.Servo().set_servo(motor_1,b)
PWM.Servo().set_servo(motor_2,b+a)
elif a>10:
a=a*10
PWM.Servo().set_servo(motor_1,b-a)
PWM.Servo().set_servo(motor_2,b)

#전체 모터 제어 함수
def ACTION(xro,yro):
if yro <=10 and xro<10 and xro>-10 :
STOP()
elif yro <=10 and xro>40 :
S_TURN(1)
elif yro <=10 and xro<-40 :
S_TURN(2)
elif yro>10 and yro<=50 and xro<10 and xro>-10 :
STRAIGHT(yro)
elif yro>10 and yro<=50 :
R_L_TURN(xro,yro)
while 1:

#가속도 값 z값을 읽어옴
accel_zout = read_word_2c(0x3f)
print “accel_zout: “, accel_zout

#읽어온 가속도 z값으로 모터 핀 번호 변경
if accel_zout>15000:
motor_1=4
motor_2=18
elif accel_zout<=15000:
motor_1=18
motor_2=4

#조종 라즈베리파이가 보낸 값을 받아옴
for i in range(1,2):
data, addr = sock1.recvfrom(1024)
if data[0]==’a’:
xro = string.atof(data[1:])
elif data[0]==’b’:
yro = string.atof(data[1:])
print(“x_rotation=%f\t” %(xro))
print(“y_rotation=%f\t” %(yro))

#받아온 값으로 부터 모터 제어실행
ACTION(xro,yro)

기대효과
해양 정찰을 할 경우 암초, 바다의 포식자, 혹은 위험 물질로 인해 갈 수 없는 곳은 사람을 대신해 배로 정찰이 가능하다. 특히 해초가 많은 곳에서 일반적인 RC보트나 배로는 모터에 해초가 감겨서 쉽게 고장날 수 있다. 이런 장소들을 ‘로꾸꺼 보트’는 모터 위치가 물 위에 있기 때문에 고장의 염려없이 갈 수 있다. 수시로 보트들이 돌아가면서 충전하고 정찰하는 시스템을 구축해 놓아 녹조·적조 현상을 감지하여 빠른 조치가 가능하고 지속적인 해양기후 탐지를 통해 배들의 안전한 운항이 가능하다.
다리를 지을 때 육지에서는 교량을 만드는 부분을 모든 각도에서 볼 수 없기 때문에 사람이 보기 힘든 각도에서 촬영하여 사람들이 쉽게 분석할 수 있도록 한다. 강물의 폭이 넓어지고 깊이가 깊어지면 수질 검사나 생태계 조사를 위해서 배를 타고 가서 조사를 하는 번거로움이 있다. 수심이 얕을 경우에도 물의 오염 정도가 심각할 경우에는 사람이 직접 들어가기 보다는 ‘로꾸꺼 보트’를 이용해 수질 검사 및 생태계 조사가 가능하다.

참고문헌
· 파이썬 UDP 프로젝트 : wiki.python.org/moin/UdpCommunication
· 파이썬 참조 : www.python.org/
· 라즈베리파이 공식 홈페이지 : www.raspberrypi.org
· jpeg 공식 홈페이지 : https://jpeg.org/

회로도

① 컨트롤러

38 ict 로꾸꺼 (19)
② 보트

38 ict 로꾸꺼 (20)

 

 

 

Leave A Comment

*