라즈베리 파이 피코 WH 장애물 회피 자동차 MicroPython 10단계 완성 자료

부품 준비부터 초음파 센서, L298N 모터 드라이버, PWM 속도 제어, 서보모터 좌우 탐색, Wi-Fi 웹 원격 제어까지 단계별로 완성합니다.

학습 목표

이 자료는 라즈베리 파이 피코 WH로 장애물 회피 자동차를 만들기 위한 전체 과정을 다룹니다. 처음에는 보드와 센서를 따로 테스트하고, 이후 모터 제어 함수를 만들며, 마지막에는 앞쪽 장애물을 감지하고 좌우 거리를 비교해 더 넓은 방향으로 이동하는 자동차를 완성합니다.

필요 부품

부품수량역할비고
Raspberry Pi Pico WH1개자동차의 두뇌Wi-Fi 가능, 헤더 핀 납땜형
HC-SR04 초음파 센서1개앞쪽 장애물 거리 측정ECHO 전압 주의
L298N 모터 드라이버1개DC 모터 제어TB6612 사용 시 핀 이름만 바꿔 응용 가능
DC 기어드 모터2개왼쪽·오른쪽 바퀴 구동자동차 키트 모터
서보모터 SG901개초음파 센서 좌우 회전외부 5V 권장
배터리 팩1개모터 전원모터 사양에 맞게 6~12V
전원 스위치1개전체 전원 차단안전용
점퍼선여러 개부품 연결수-수, 암-수 혼합
저항 1kΩ, 2kΩ각 1개ECHO 전압 분배5V → 약 3.3V 낮춤

전체 배선 요약

장치장치 핀Pico WH 핀설명
HC-SR04TRIGGP2초음파 발사 신호
HC-SR04ECHOGP3초음파 반사 입력, 전압 분배 권장
L298NIN1GP10왼쪽 모터 방향 1
L298NIN2GP11왼쪽 모터 방향 2
L298NIN3GP12오른쪽 모터 방향 1
L298NIN4GP13오른쪽 모터 방향 2
L298NENAGP14왼쪽 모터 속도 PWM
L298NENBGP15오른쪽 모터 속도 PWM
SG90 서보SignalGP16초음파 센서 좌우 회전
공통GNDGNDPico, 센서, 드라이버, 배터리 GND 공통

중요 주의사항

제작 순서

① Pico WH LED 테스트 → ② 초음파 센서 테스트 → ③ 모터 단독 테스트 → ④ 전진·후진·회전 함수 작성 → ⑤ PWM 속도 제어 → ⑥ 장애물 정지 → ⑦ 후진 후 회전 → ⑧ 서보 좌우 탐색 → ⑨ 좌우 비교 회피 완성 → ⑩ Wi-Fi 원격 출발·정지

1단계

부품 준비와 Pico WH 내장 LED 테스트

목적
Pico WH 보드와 MicroPython 실행 환경이 정상인지 확인합니다. Pico WH는 보드 위에 LED가 있어 별도 LED 없이 테스트할 수 있습니다.
배선
Pico WH만 USB로 PC에 연결
from machine import Pin
from time import sleep

led = Pin("LED", Pin.OUT)

while True:
    led.on()
    print("LED 켜짐")
    sleep(0.5)

    led.off()
    print("LED 꺼짐")
    sleep(0.5)
확인 포인트
  • Thonny 오른쪽 아래 인터프리터가 MicroPython(Raspberry Pi Pico)인지 확인합니다.
  • Pico WH 내장 LED가 0.5초 간격으로 깜빡이면 보드와 업로드 환경이 정상입니다.
  • Pico 일반 버전은 Pin(25, Pin.OUT)을 사용할 수 있지만 Pico WH는 Pin('LED')가 편리합니다.
2단계

초음파 센서 HC-SR04 거리 측정

목적
자동차의 눈 역할을 하는 초음파 센서가 앞쪽 장애물까지의 거리를 제대로 읽는지 확인합니다.
배선
HC-SR04 VCC→5V, GND→GND, TRIG→GP2, ECHO→GP3
from machine import Pin, time_pulse_us
from time import sleep_us, sleep

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

def get_distance_cm():
    trig.low()
    sleep_us(2)

    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)

    if duration < 0:
        return 999

    distance = duration * 0.0343 / 2
    return distance

while True:
    distance = get_distance_cm()
    print("거리:", round(distance, 1), "cm")
    sleep(0.3)
확인 포인트
  • 손이나 책을 센서 앞에 가까이 가져가면 거리값이 작아져야 합니다.
  • 값이 계속 999이면 ECHO, TRIG 배선을 다시 확인합니다.
  • HC-SR04 ECHO가 5V로 나오는 모듈은 저항 분배로 3.3V 이하로 낮추는 것이 안전합니다.
3단계

모터 드라이버 L298N 기본 배선 테스트

목적
왼쪽 모터와 오른쪽 모터가 각각 정방향·역방향으로 움직이는지 확인합니다.
배선
L298N IN1→GP10, IN2→GP11, IN3→GP12, IN4→GP13, GND 공통
from machine import Pin
from time import sleep

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

def left_forward():
    in1.high()
    in2.low()

def left_backward():
    in1.low()
    in2.high()

def right_forward():
    in3.high()
    in4.low()

def right_backward():
    in3.low()
    in4.high()

def stop_all():
    in1.low()
    in2.low()
    in3.low()
    in4.low()

while True:
    print("왼쪽 모터 전진")
    left_forward()
    sleep(1)
    stop_all()
    sleep(0.5)

    print("왼쪽 모터 후진")
    left_backward()
    sleep(1)
    stop_all()
    sleep(0.5)

    print("오른쪽 모터 전진")
    right_forward()
    sleep(1)
    stop_all()
    sleep(0.5)

    print("오른쪽 모터 후진")
    right_backward()
    sleep(1)
    stop_all()
    sleep(1)
확인 포인트
  • 한쪽 모터가 반대로 돌면 모터 단자 두 선을 서로 바꾸거나 코드의 high/low를 바꿉니다.
  • 모터 전원은 Pico WH의 3.3V 핀이 아니라 별도 배터리와 모터 드라이버로 공급합니다.
  • 배터리 GND, L298N GND, Pico WH GND는 반드시 공통 연결합니다.
4단계

자동차 기본 동작 함수 만들기

목적
전진, 후진, 좌회전, 우회전, 정지를 함수로 정리해 이후 코드에서 쉽게 재사용합니다.
배선
3단계와 같은 모터 드라이버 배선
from machine import Pin
from time import sleep

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

def stop():
    in1.low()
    in2.low()
    in3.low()
    in4.low()

def forward():
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def backward():
    in1.low()
    in2.high()
    in3.low()
    in4.high()

def turn_left():
    in1.low()
    in2.high()
    in3.high()
    in4.low()

def turn_right():
    in1.high()
    in2.low()
    in3.low()
    in4.high()

while True:
    forward()
    print("전진")
    sleep(1.5)

    stop()
    sleep(0.5)

    backward()
    print("후진")
    sleep(1)

    stop()
    sleep(0.5)

    turn_left()
    print("좌회전")
    sleep(0.8)

    turn_right()
    print("우회전")
    sleep(0.8)

    stop()
    sleep(1)
확인 포인트
  • 자동차가 전진 명령에서 뒤로 가면 양쪽 모터 방향을 함께 바꿔야 합니다.
  • 좌회전·우회전 시간이 길수록 회전 각도가 커집니다.
  • 바퀴, 바닥 마찰, 배터리 전압에 따라 sleep 시간을 조절합니다.
5단계

PWM으로 모터 속도 제어하기

목적
ENA, ENB 핀을 사용해 양쪽 모터 속도를 조절합니다. 장애물 회피 자동차가 너무 빠르면 센서 판단 전에 충돌할 수 있습니다.
배선
IN1→GP10, IN2→GP11, IN3→GP12, IN4→GP13, ENA→GP14, ENB→GP15
from machine import Pin, PWM
from time import sleep

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

ena = PWM(Pin(14))
enb = PWM(Pin(15))
ena.freq(1000)
enb.freq(1000)

def set_speed(left, right):
    left = max(0, min(65535, left))
    right = max(0, min(65535, right))
    ena.duty_u16(left)
    enb.duty_u16(right)

def forward(speed=35000):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def stop():
    set_speed(0, 0)
    in1.low()
    in2.low()
    in3.low()
    in4.low()

while True:
    print("느리게 전진")
    forward(25000)
    sleep(2)

    print("빠르게 전진")
    forward(50000)
    sleep(2)

    stop()
    sleep(1)
확인 포인트
  • L298N의 ENA/ENB 점퍼가 꽂혀 있으면 PWM 제어가 안 될 수 있으므로 점퍼를 제거하고 Pico WH에 연결합니다.
  • 속도값은 0~65535 범위입니다.
  • 양쪽 모터 속도가 다르면 left, right 값을 다르게 보정합니다.
6단계

장애물이 가까우면 정지하기

목적
센서와 모터를 결합하는 첫 번째 단계입니다. 앞에 장애물이 가까우면 자동차가 멈춥니다.
배선
초음파 TRIG→GP2, ECHO→GP3 / 모터는 5단계 배선
from machine import Pin, PWM, time_pulse_us
from time import sleep_us, sleep

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

ena = PWM(Pin(14))
enb = PWM(Pin(15))
ena.freq(1000)
enb.freq(1000)

def get_distance_cm():
    trig.low()
    sleep_us(2)
    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)
    if duration < 0:
        return 999

    return duration * 0.0343 / 2

def set_speed(left, right):
    ena.duty_u16(left)
    enb.duty_u16(right)

def forward(speed=35000):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def stop():
    set_speed(0, 0)
    in1.low()
    in2.low()
    in3.low()
    in4.low()

while True:
    distance = get_distance_cm()
    print("앞 거리:", round(distance, 1), "cm")

    if distance < 18:
        stop()
        print("장애물 감지 → 정지")
    else:
        forward(33000)
        print("안전 → 전진")

    sleep(0.1)
확인 포인트
  • 장애물 기준 거리 18cm는 교실 환경에 맞게 15~30cm 사이에서 조절합니다.
  • 차가 너무 빠르면 기준 거리를 더 크게 잡습니다.
  • 정지 후 계속 떨리면 센서값 평균을 내거나 sleep 시간을 늘립니다.
7단계

장애물이 가까우면 후진 후 회전하기

목적
가까운 장애물을 만나면 멈추고, 뒤로 물러난 뒤 오른쪽으로 회전하여 회피합니다.
배선
초음파와 PWM 모터 배선은 6단계와 같습니다.
from machine import Pin, PWM, time_pulse_us
from time import sleep_us, sleep

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

ena = PWM(Pin(14))
enb = PWM(Pin(15))
ena.freq(1000)
enb.freq(1000)

def get_distance_cm():
    trig.low()
    sleep_us(2)
    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)
    if duration < 0:
        return 999

    return duration * 0.0343 / 2

def set_speed(left, right):
    ena.duty_u16(left)
    enb.duty_u16(right)

def stop():
    set_speed(0, 0)
    in1.low()
    in2.low()
    in3.low()
    in4.low()

def forward(speed=33000):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def backward(speed=32000):
    set_speed(speed, speed)
    in1.low()
    in2.high()
    in3.low()
    in4.high()

def turn_right(speed=35000):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.low()
    in4.high()

while True:
    distance = get_distance_cm()
    print("거리:", round(distance, 1), "cm")

    if distance < 20:
        print("장애물 → 정지")
        stop()
        sleep(0.2)

        print("후진")
        backward()
        sleep(0.5)

        print("우회전")
        turn_right()
        sleep(0.65)

        stop()
        sleep(0.2)
    else:
        forward()

    sleep(0.05)
확인 포인트
  • 후진 시간이 길면 장애물에서 더 멀어집니다.
  • 회전 시간이 길면 더 많이 방향을 바꿉니다.
  • 오른쪽으로만 회전하면 같은 상황에서 반복적으로 걸릴 수 있으므로 다음 단계에서 좌우 판단을 추가합니다.
8단계

서보모터로 초음파 센서 좌우 살피기

목적
초음파 센서를 서보모터 위에 올려 왼쪽, 앞, 오른쪽 거리를 비교합니다.
배선
서보 신호선→GP16, VCC→외부 5V, GND→공통 GND / 초음파는 6단계와 같음
from machine import Pin, PWM, time_pulse_us
from time import sleep_us, sleep

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

servo = PWM(Pin(16))
servo.freq(50)

def set_angle(angle):
    min_duty = 1638
    max_duty = 8192
    duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
    servo.duty_u16(duty)
    sleep(0.35)

def get_distance_cm():
    trig.low()
    sleep_us(2)
    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)
    if duration < 0:
        return 999

    return duration * 0.0343 / 2

while True:
    set_angle(150)
    left = get_distance_cm()

    set_angle(90)
    front = get_distance_cm()

    set_angle(30)
    right = get_distance_cm()

    set_angle(90)

    print("왼쪽:", round(left, 1), "cm")
    print("앞쪽:", round(front, 1), "cm")
    print("오른쪽:", round(right, 1), "cm")
    print("-" * 25)

    sleep(1)
확인 포인트
  • 서보가 떨리면 외부 5V 전원을 사용하고 GND를 공통 연결합니다.
  • 각도 150, 90, 30은 장착 방향에 따라 140, 90, 40처럼 조절할 수 있습니다.
  • 센서가 왼쪽을 볼 때 오른쪽 값이 나오는 경우 서보 장착 방향을 확인합니다.
9단계

좌우 거리 비교 장애물 회피 자동차 완성

목적
앞이 막히면 왼쪽과 오른쪽을 비교하고 더 넓은 방향으로 회전합니다.
배선
초음파 TRIG→GP2, ECHO→GP3 / IN1~IN4→GP10~GP13 / ENA→GP14 / ENB→GP15 / 서보→GP16
from machine import Pin, PWM, time_pulse_us
from time import sleep_us, sleep

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

ena = PWM(Pin(14))
enb = PWM(Pin(15))
ena.freq(1000)
enb.freq(1000)

servo = PWM(Pin(16))
servo.freq(50)

SAFE_DISTANCE = 22
DRIVE_SPEED = 33000
TURN_SPEED = 37000

def set_angle(angle):
    min_duty = 1638
    max_duty = 8192
    duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
    servo.duty_u16(duty)
    sleep(0.35)

def get_distance_cm():
    trig.low()
    sleep_us(2)
    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)
    if duration < 0:
        return 999

    return duration * 0.0343 / 2

def set_speed(left, right):
    left = max(0, min(65535, left))
    right = max(0, min(65535, right))
    ena.duty_u16(left)
    enb.duty_u16(right)

def stop():
    set_speed(0, 0)
    in1.low()
    in2.low()
    in3.low()
    in4.low()

def forward(speed=DRIVE_SPEED):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def backward(speed=DRIVE_SPEED):
    set_speed(speed, speed)
    in1.low()
    in2.high()
    in3.low()
    in4.high()

def turn_left(speed=TURN_SPEED):
    set_speed(speed, speed)
    in1.low()
    in2.high()
    in3.high()
    in4.low()

def turn_right(speed=TURN_SPEED):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.low()
    in4.high()

def look_left():
    set_angle(150)
    return get_distance_cm()

def look_front():
    set_angle(90)
    return get_distance_cm()

def look_right():
    set_angle(30)
    return get_distance_cm()

set_angle(90)

while True:
    front = look_front()
    print("앞:", round(front, 1), "cm")

    if front > SAFE_DISTANCE:
        forward()
    else:
        stop()
        sleep(0.2)

        backward()
        sleep(0.35)
        stop()
        sleep(0.2)

        left = look_left()
        right = look_right()
        set_angle(90)

        print("왼쪽:", round(left, 1), "cm / 오른쪽:", round(right, 1), "cm")

        if left > right:
            print("왼쪽으로 회피")
            turn_left()
            sleep(0.65)
        else:
            print("오른쪽으로 회피")
            turn_right()
            sleep(0.65)

        stop()
        sleep(0.1)
확인 포인트
  • 가장 완성도 높은 기본 장애물 회피 코드입니다.
  • SAFE_DISTANCE, DRIVE_SPEED, TURN_SPEED만 바꿔도 자동차 성격이 크게 달라집니다.
  • 교실 바닥에서 직진이 휘면 왼쪽·오른쪽 PWM 값을 다르게 주어 보정합니다.
10단계

Pico WH Wi-Fi 웹 원격 정지·출발 기능 추가

목적
Pico WH의 Wi-Fi 기능을 이용해 휴대폰이나 PC 브라우저에서 자동차를 시작·정지합니다. 장애물 회피 로직은 9단계와 결합했습니다.
배선
9단계 전체 배선 + Wi-Fi 공유기 접속 정보 필요
import network
import socket
from machine import Pin, PWM, time_pulse_us
from time import sleep_us, sleep

SSID = "와이파이이름"
PASSWORD = "와이파이비밀번호"

trig = Pin(2, Pin.OUT)
echo = Pin(3, Pin.IN)

in1 = Pin(10, Pin.OUT)
in2 = Pin(11, Pin.OUT)
in3 = Pin(12, Pin.OUT)
in4 = Pin(13, Pin.OUT)

ena = PWM(Pin(14))
enb = PWM(Pin(15))
ena.freq(1000)
enb.freq(1000)

servo = PWM(Pin(16))
servo.freq(50)

led = Pin("LED", Pin.OUT)

running = False
SAFE_DISTANCE = 22
DRIVE_SPEED = 32000
TURN_SPEED = 36000

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)

    print("Wi-Fi 연결 중...")
    while not wlan.isconnected():
        led.toggle()
        sleep(0.3)

    led.on()
    ip = wlan.ifconfig()[0]
    print("연결 완료 IP:", ip)
    return ip

def set_angle(angle):
    min_duty = 1638
    max_duty = 8192
    duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
    servo.duty_u16(duty)
    sleep(0.25)

def get_distance_cm():
    trig.low()
    sleep_us(2)
    trig.high()
    sleep_us(10)
    trig.low()

    duration = time_pulse_us(echo, 1, 30000)
    if duration < 0:
        return 999

    return duration * 0.0343 / 2

def set_speed(left, right):
    ena.duty_u16(max(0, min(65535, left)))
    enb.duty_u16(max(0, min(65535, right)))

def stop():
    set_speed(0, 0)
    in1.low()
    in2.low()
    in3.low()
    in4.low()

def forward(speed=DRIVE_SPEED):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.high()
    in4.low()

def backward(speed=DRIVE_SPEED):
    set_speed(speed, speed)
    in1.low()
    in2.high()
    in3.low()
    in4.high()

def turn_left(speed=TURN_SPEED):
    set_speed(speed, speed)
    in1.low()
    in2.high()
    in3.high()
    in4.low()

def turn_right(speed=TURN_SPEED):
    set_speed(speed, speed)
    in1.high()
    in2.low()
    in3.low()
    in4.high()

def avoid_once():
    front = get_distance_cm()

    if front > SAFE_DISTANCE:
        forward()
        return front

    stop()
    sleep(0.15)

    backward()
    sleep(0.3)
    stop()

    set_angle(150)
    left = get_distance_cm()

    set_angle(30)
    right = get_distance_cm()

    set_angle(90)

    if left > right:
        turn_left()
        sleep(0.6)
    else:
        turn_right()
        sleep(0.6)

    stop()
    return front

def web_page(ip, distance):
    state = "주행 중" if running else "정지"
    return f"""HTTP/1.1 200 OK

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>Pico WH 장애물 회피 자동차</title>
<style>
body {{
    font-family: Arial, sans-serif;
    text-align: center;
    padding: 40px;
}}
a {{
    display: inline-block;
    margin: 12px;
    padding: 18px 26px;
    border-radius: 14px;
    color: white;
    text-decoration: none;
    font-size: 24px;
}}
.start {{ background: #16a34a; }}
.stop {{ background: #dc2626; }}
</style>
</head>
<body>
<h1>Pico WH 장애물 회피 자동차</h1>
<h2>상태: {state}</h2>
<h2>최근 거리: {distance:.1f} cm</h2>
<p>접속 주소: http://{ip}</p>
<a class="start" href="/start">출발</a>
<a class="stop" href="/stop">정지</a>
</body>
</html>
"""

ip = connect_wifi()
set_angle(90)

addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(1)
server.settimeout(0.05)

last_distance = 999
print("브라우저에서 접속:", "http://" + ip)

while True:
    try:
        client, address = server.accept()
        request = client.recv(1024).decode()

        if "GET /start" in request:
            running = True

        if "GET /stop" in request:
            running = False
            stop()

        response = web_page(ip, last_distance)
        client.send(response)
        client.close()

    except OSError:
        pass

    if running:
        last_distance = avoid_once()
    else:
        stop()
        sleep(0.05)
확인 포인트
  • SSID와 PASSWORD를 실제 Wi-Fi 정보로 바꿉니다.
  • Thonny Shell에 출력되는 IP 주소를 휴대폰 또는 PC 브라우저에 입력합니다.
  • 같은 Wi-Fi에 연결된 기기에서만 접속되는 경우가 많습니다.
  • 주행 중 긴급 정지를 위해 정지 버튼이 잘 작동하는지 바퀴를 띄운 상태에서 먼저 테스트합니다.