참조
lego education pybricks 공식 e-manual | python 코딩에 필요한 라이브러리와 클래스가 정리되어 있다.
ev3와 PC의 연결에는 크게 두 가지 방법이 있다.
유선
ev3 본체의 pc 포트와 PC의 USB 포트를 연결하기
블루투스
ev3 메뉴에서 블루투스 설정에 들어가서, pc와 페어링 하기
블루투스 페어링을 한 번 해두면, 이후 PC 블루투스를 켜 둔 상태에서
ev3 전원을 켜기만 해도 자동으로 다시 연결되기 때문에, 블루투스 연결을 강력히 추천한다.
유선 또는 무선 연결을 확인하면 파일 탭의 EV3DEV DEVICE BROWSER 에서
Click here to connect를 누르고 위쪽의 옵션을 눌러 VS code 와 ev3 를 연동할 수 있다.
내가 진행한 프로젝트는 3 센서를 사용한 라인트레이싱이고, 초음파 센서와 제작한 집게를 이용하여 물체를 집는 것이다.
프로젝트 과제에 맞게 어느정도 하드코딩 되어 있기 때문에, 참고 정도로만 이용하길 권한다.
또한 시간이 부족하여 PID 값 최적화가 약간 부족하다.
#!/usr/bin/env pybricks-micropython
# 최종 수정 5/29
from pybricks.hubs import EV3Brick
from pybricks.ev3devices import Motor, ColorSensor, UltrasonicSensor
from pybricks.parameters import Port, Button
from pybricks.tools import wait
from pybricks.robotics import DriveBase
# === 설정 ===
EV3_SPEED = 100 # 라인트레이싱 전진 속도
FORWARD_SPEED = 10 # 회전 중 전진 속도
TURN_SPEED = 30 # 회전 속도
# 전진 거리 (색상 감지 시 / 커브 감지 시)
STRAIGHT_DISTANCE = 20 # 색상 감지 후 전진 거리 (mm)
STRAIGHT_CURVE_DISTANCE = 10 # 커브 진입 후 전진 거리 (mm)
# 회전 속도
TURN_RATE_COLOR_RED = -60 # R
TURN_RATE_COLOR_YELLOW = 60 # Y
TURN_RATE_COLOR_BLUE = 80 # B
TURN_RATE_CURVE_LEFT = -50 # Left
TURN_RATE_CURVE_RIGHT = 50 # Right
# 예외 인덱스
RED_COUNT = 0 # 4번째 RED에서 예외발생: 직진
YELLOW_COUNT = 0 # 4번째 YELLOW에서 예외발생: 직진
EXCEPTION = False # 예외발생시 True
sticker_index = 0 # 현재 스티커 센 개수. RBRRBRYBYBYYBYRG
# 집게 동작 각도
CLAW_CLOSE_ANGLE = 50 # 닫힘 각도
CLAW_OPEN_ANGLE = 0 # 열림 각도(초기값)
I_HAVE_OBJECT = False # 물체를 집고 있을 때 True
# 초기화
ev3 = EV3Brick()
claw_motor = Motor(Port.D) # 집게
left_motor = Motor(Port.B) # 왼바퀴
right_motor = Motor(Port.C) # 오른바퀴
ultrasonic = UltrasonicSensor(Port.S1) # 초음파 센서
sensor_L = ColorSensor(Port.S2)
sensor_C = ColorSensor(Port.S3)
sensor_R = ColorSensor(Port.S4)
robot = DriveBase(left_motor, right_motor, wheel_diameter=55.5, axle_track=104)
def close_claw(): # 집게 닫기, 잠깐 정지, 통 획득 인덱스 true
global I_HAVE_OBJECT
robot.stop()
wait(500)
claw_motor.run_target(200, CLAW_CLOSE_ANGLE)
I_HAVE_OBJECT = True
wait(500)
def open_claw(): # 집게 열기, 1초 정지, 통 획득 인덱스 false
global I_HAVE_OBJECT
robot.stop()
wait(500)
claw_motor.run_target(200, CLAW_OPEN_ANGLE)
I_HAVE_OBJECT = False
ev3.speaker.beep() # 통 놓고 비프음 발생
wait(1000) # 사람이 통을 뺄 시간 1초
def wait_button_press():
while Button.CENTER not in ev3.buttons.pressed():
wait(10)
while Button.CENTER in ev3.buttons.pressed():
wait(10)
def get_reference_colors():
reference = {}
for name in ["Red", "Yellow", "Blue"]:
ev3.screen.clear()
ev3.screen.print("Show " + name)
wait_button_press()
rgb = sensor_C.rgb()
reference[name] = rgb
ev3.screen.clear()
ev3.screen.print(name + ": " + str(rgb))
ev3.speaker.beep()
return reference
# ---------------------------------색상 체크 함수----------------------------------
def identify_color(rgb, reference, tolerance=17):
global sticker_index
r, g, b = rgb
for name, ref in reference.items():
rr, gg, bb = tuple(ref)
if (abs(r - rr) <= tolerance and
abs(g - gg) <= tolerance and
abs(b - bb) <= tolerance):
if name.upper() == "BLUE": # BLUE인 경우 오차를 줄여서 다시 판단, 미충족시 검은색으로 판단
tight_tol = 12
if (abs(r - rr) <= tight_tol and
abs(g - gg) <= tight_tol and
abs(b - bb) <= tight_tol):
return "BLUE"
else:
continue
elif (name.upper() == "YELLOW") and (sticker_index < 7): # 6번 째 이하 스티커일때 노란색이면 무시
continue
else:
return name.upper()
return "UNKNOWN"
def calibrate():
ev3.speaker.beep()
ev3.screen.print("Put on BLACK")
wait_button_press()
black_value = sensor_C.reflection()
ev3.speaker.beep()
wait(500)
ev3.screen.clear()
ev3.screen.print("Put on WHITE")
wait_button_press()
white_value = sensor_C.reflection()
ev3.speaker.beep()
wait(500)
ev3.screen.clear()
ev3.screen.print("Ready!")
wait_button_press()
ev3.speaker.beep()
wait(2000)
return black_value, white_value
def linetracing(black_value, white_value, reference_colors):
global RED_COUNT, YELLOW_COUNT, EXCEPTION, sticker_index
# ------------------------------------------PID 값------------------------------------------------
Kp = 1.5
Ki = 0.05
Kd = 0.043
integral = 0
last_error = 0
curve_counter = 0
CURVE_INT = 3 # 10 ms 가 몇 회?
# ✅ 1초간 L/R 센서값 저장을 위한 2행 50열 행렬
N = 100
sensor_history = [[0]*N, [0]*N]
index = 0
while True:
L = sensor_L.reflection()
C = sensor_C.reflection()
R = sensor_R.reflection()
# ✅ 현재 센서값을 저장 (가장 최근 값은 index-1 위치)
sensor_history[0][index] = L
sensor_history[1][index] = R
current_index = index
index = (index + 1) % N
rgb = sensor_C.rgb()
detected_color = identify_color(rgb, reference_colors)
# 초음파 센서에 통이 감지됨 -> 집게 닫기
if I_HAVE_OBJECT == False and ultrasonic.distance() < 50: # mm 단위, 5cm
close_claw()
# ✅ 조건 1: 색상 감지 → 정지 → say → 전진 → 회전 → 검은색 감지
if detected_color in ["RED", "YELLOW", "BLUE"]:
robot.stop()
wait(100)
if detected_color == "RED":
ev3.speaker.say("Red")
turn_rate = TURN_RATE_COLOR_RED
RED_COUNT += 1 # RED 감지할 때마다 RED_COUNT ++
sticker_index += 1
if RED_COUNT == 4:
robot.drive(FORWARD_SPEED, 0)
ev3.speaker.beep() # 예외 - 비프음 발생
EXCEPTION = True # 예외발생, 직진
RED_COUNT += 1 # 이후 작동 방지를 위한 +1 처리
elif detected_color == "YELLOW":
ev3.speaker.say("Yellow")
turn_rate = TURN_RATE_COLOR_YELLOW
YELLOW_COUNT += 1 # YELLOW 감지할 때마다 YELLOW_COUNT ++
sticker_index += 1
if YELLOW_COUNT == 4:
robot.drive(FORWARD_SPEED, 0)
ev3.speaker.beep() # 예외 - 비프음 발생
EXCEPTION = True # 예외발생, 직진
YELLOW_COUNT += 1 # 이후 작동 방지를 위한 +1 처리
elif detected_color == "BLUE":
ev3.speaker.say("Blue")
open_claw() # BLUE 음성 이후 집게 열고 1초 대기
turn_rate = TURN_RATE_COLOR_BLUE
sticker_index += 1
if EXCEPTION == True:
wait(1000) # 예외발생시 회전 막고 wait 1초
EXCEPTION = False # 예외종료
else:
# 전진 후 회전
robot.straight(STRAIGHT_DISTANCE)
if detected_color == "BLUE":
robot.drive(0, turn_rate)
else:
robot.drive(TURN_SPEED, turn_rate)
wait(600)
while sensor_C.reflection() > black_value + 5:
wait(10)
robot.stop()
continue
# ✅ 조건 2: 커브 감지 → 전진 후 회전 시작 → 검은색 감지까지
if C >= (white_value - 5):
curve_counter += 1
if curve_counter >= CURVE_INT: # 0.25초 경과
ev3.speaker.beep()
robot.stop()
# ✅ 현재 L/R의 차이가 거의 없을 때 → 과거로 거슬러가며 차이 10 이상인 시점 찾아 판단
if abs(L - R) <= 10:
found = False
for i in range(1, N + 1):
check_index = (current_index - i + N) % N
past_L = sensor_history[0][check_index]
past_R = sensor_history[1][check_index]
if abs(past_L - past_R) >= 10:
found = True
if past_L > past_R:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_RIGHT)
else:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_LEFT)
break
# ✅ 판단 기준 못 찾은 경우 현재값으로 처리
if not found:
if L > R:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_RIGHT)
else:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_LEFT)
else: # L R에 값 차이가 있으면 현재 값 기준으로 판단
# 현재 값 기준 판단
if L > R:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_RIGHT)
else:
robot.straight(STRAIGHT_CURVE_DISTANCE)
robot.drive(FORWARD_SPEED, TURN_RATE_CURVE_LEFT)
while sensor_C.reflection() > black_value + 5:
wait(10)
robot.stop()
curve_counter = 0
continue
else:
if curve_counter > 0:
curve_counter -= 1
# ✅ PID 라인트레이싱
error = L - R
integral += error
derivative = error - last_error
turn_rate = Kp * error + Ki * integral + Kd * derivative
robot.drive(EV3_SPEED, turn_rate)
last_error = error
wait(10)
# 실행
ev3.speaker.beep()
reference_colors = get_reference_colors()
black_value, white_value = calibrate()
linetracing(black_value, white_value, reference_colors)
코드를 실행하기 위해서는 디버깅 창에 들어가서 Download 왼쪽에 있는 Start Debugging 을 누르면 된다.
각종 예상치 못한 사이드 이펙트도 많이 발생하였고, 특성을 이해하는데 오래걸렸다.
디버깅에 가장 큰 시간을 소비한 문제는 ev3.speaker.say() 함수였는데,
소리가 나오고 있는 동안에는 다음 코드로 넘어가지 않는다는 점을 아는데 오래 걸렸다.
gpt 사용에 대해서는,
코드를 작성하는데에 gpt 로는 초안만 잡아놓고, 직접 라이브러리 문서를 읽어가며 작성을 하는게 최종적으로는 더 적은 시간이 걸린다.
python 코드를 작성하는 연습을 하는데 좋은 프로젝트였다.