마이크로파이썬으로 구현하는 28BYJ-48 스텝 모터 정밀 구동법

안녕하세요! 오늘은 작지만 강력한 ESP32-C3 Mini 보드를 사용해서 스텝모터를 돌려보겠습니다. 일반 아두이노보다 성능이 좋고 크기가 작아 실제 전시물 제작에 아주 유리한 보드죠.

최근 제가 제작하고 있는 ‘음악 나오는 전시용 회전판(턴테이블)’ 프로젝트에서도 이 스텝모터가 핵심적인 역할을 담당하고 있는데요. 왜 일반 모터가 아닌 스텝모터를 사용했는지, 그 이유와 특징을 정리해 드립니다.

1. 스텝모터란?

일반적인 DC 모터는 전원을 연결하면 뱅글뱅글 계속 회전하지만, 스텝모터는 이름처럼 ‘스텝(Step, 단계)’을 밟듯이 딱 정해진 각도만큼만 움직이는 모터입니다.

예를 들어, “90도만 움직여!” 혹은 “한 바퀴를 200번에 나눠서 움직여!”라는 명령을 아주 정확하게 수행할 수 있습니다.

2. 스텝모터의 최대 장점

  • 정밀한 제어: 각도를 아주 미세하게 조절할 수 있어 로봇 팔, 3D 프린터, 그리고 제가 만든 회전판처럼 정확한 위치에 멈춰야 하는 장치에 필수입니다.
  • 강력한 홀딩 토크(Holding Torque): 특정 각도에서 멈춰 있을 때, 외부 힘에 의해 돌아가지 않도록 꽉 붙잡고 있는 힘이 강합니다.
  • 반복 정밀도: 같은 명령을 내리면 언제나 똑같은 위치로 돌아옵니다.

3. 주요 활용 사례

  • 3D 프린터: 노즐을 0.1mm 단위로 움직일 때 사용됩니다.
  • CCTV 카메라: 원하는 방향으로 렌즈를 정확히 돌릴 때 쓰입니다.
  • 전시용 턴테이블: 일정한 속도로 회전하거나, 특정 제품 위치에서 잠시 멈추는 연출을 할 때 최적입니다.

4. 사용법

준비물

  • ESP32-C3 SuperMini (또는 Mini 보드)
  • 28BYJ-48 스텝모터
  • ULN2003 모터 드라이버
  • 점퍼 와이어

28BYJ-48 스텝모터스텝모터를 제어할 때 ULN2003 드라이버를 사용하는데

  1. 전류 증폭 (강력한 근육 역할) ESP32-C3의 제어 신호는 너무 약해서 모터를 직접 돌릴 힘이 없습니다. 드라이버는 이 약한 신호를 받아 모터를 구동할 수 있는 큰 전류로 바꿔줍니다.
  2. 보드 보호 (방패 역할) 모터가 멈출 때 발생하는 고전압(역기전력)이 거꾸로 흘러 들어와 비싼 메인 보드가 타버리는 것을 방지합니다.
  3. 효율적인 전원 관리 보드는 3.3V 전압으로 신호만 보내고, 모터는 5V 이상의 전원을 따로 쓸 수 있게 해줌으로써 시스템의 안정성을 높여줍니다.

ESP32-C3 SuperMini 보드의 GPIO 1~4번 핀을 사용합니다.

ULN2003 드라이버ESP32-C3 GPIO
IN1Pin 1
IN2Pin 2
IN3Pin 3
IN4Pin 4
VCC / GND5V / GND
28BYJ-48 스텝모터ULN2003 드라이버 조합

5. 사용 코드

step.py set_run(speed) 함수

# esp32-c3 1~4핀과 uln2003의 in1~in4 간에 연결

import machine
import time
import _thread

# 핀 설정 (IN1, IN2, IN3, IN4)
pins = [machine.Pin(i, machine.Pin.OUT) for i in [1, 2, 3, 4]]

# 8단계 Half-step 시퀀스
sequence = [
    [1, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0],
    [0, 0, 1, 0], [0, 0, 1, 1], [0, 0, 0, 1], [1, 0, 0, 1]
]

# 전역 변수: 현재 속도 저장 (0이면 정지)
current_speed = 0
stop = 0  # 1이 되면 모든 동작 중지

def motor_thread():
    """ 백그라운드에서 실제로 모터를 돌리는 쓰레드 함수 """
    global current_speed
    step_idx = 0
    
    while True:
        if current_speed == 0:
            # 정지 상태일 때 전원 차단 및 대기
            for pin in pins:
                pin.value(0)
            time.sleep(0.1)
            continue
            
        # 방향 및 속도 계산
        direction = 1 if current_speed > 0 else -1
        delay = (11 - abs(current_speed)) * 0.002
        
        # 스텝 이동
        step_idx = (step_idx + direction) % 8
        for i in range(4):
            pins[i].value(sequence[step_idx][i])
            
        time.sleep(delay)



def step_run(speed):
    """ 한번 호출하면 전역 변수 값을 바꿔서 쓰레드가 계속 돌게 함 """
    global current_speed
    # 범위 제한 (-10 ~ 10)
    if speed > 10: speed = 10
    if speed < -10: speed = -10
    
    current_speed = speed
    print(f"모터 속도가 {current_speed}(으)로 설정되었습니다.")


def test():
    """ 30초 정회전, 30초 역회전 무한 반복 """
    global stop
    stop = 0
    print("전시용 반복 테스트 시작 (stop = 1 시 중단)")

    # (속도, 유지시간) 쌍으로 리스트 구성
    scenarios = [(8, 30), (-8, 30)]

    try:
        while not stop: # stop이 0인 동안만 작동
            for speed, duration in scenarios:
                if stop: break
                
                print(f"{'정회전' if speed > 0 else '역회전'} 중 (Speed {speed})...")
                step_run(speed)
                
                # 1초마다 stop 여부를 체크하며 대기
                for _ in range(duration):
                    if stop: break
                    time.sleep(1)
                    
        # 루프 탈출 시 정지
        step_run(0)
        print("테스트 모드 종료")

    except KeyboardInterrupt:
        stop = 1
        step_run(0)


# 모터 쓰레드 시작 (프로그램 시작 시 한 번만 실행)
_thread.start_new_thread(motor_thread, ())

step_run(0)  # 정지


if __name__ == "__main__" :
    test()
# pcf8575 로 28byj-48 모터 4개를 제어 하려고 한다.
# 0~3, 4~7, 8~11, 12~15 씩 스텝모터 4개 핀에 각각 연결해놨다.
# scl 1번핀, sda 2번핀
# 스텝모터 구동하는 함수 motor(sp1, sp2,sp3,sp4) 로 스텝모터 4개의 속도를 지정
 

import machine
import time

# I2C 설정
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(2), freq=1000000)

print(i2c.scan())

PCF8575_ADDR = 0x20

# 8스텝 패턴
STEP_SEQ = [0b1000, 0b1100, 0b0100, 0b0110, 0b0010, 0b0011, 0b0001, 0b1001]
#STEP_SEQ = [0b1000, 0b0100, 0b0010, 0b0001]

# 각 모터의 상태 저장
current_indices = [0, 0, 0, 0]
last_step_time = [0, 0, 0, 0]

def write_pcf8575(data16):
    buf = bytearray(2)
    buf[0] = data16 & 0xFF
    buf[1] = (data16 >> 8) & 0xFF
    i2c.writeto(PCF8575_ADDR, buf)

def stop_all():
    """모든 모터의 전류를 차단하여 정지 및 발열 방지"""
    # PCF8575의 16개 핀(P0~P17)을 모두 0으로 설정
    write_pcf8575(0x0000)
    print("All motors stopped and powered down.")
    
def motor(sp1, sp2, sp3, sp4, duration_ms=30000):
    """
    sp값이 클수록 빠름 (예: 1~100)
    0이면 정지, 음수면 역방향
    """
    speeds = [sp1, sp2, sp3, sp4]
    
    # 속도를 주기(ms)로 변환 (속도 5는 속도 1보다 5배 짧은 대기시간)
    # 0으로 나누기 방지를 위해 아주 작은 값 처리
    intervals = []
    for s in speeds:
        if s == 0:
            intervals.append(0)
        else:
            intervals.append(1000 // abs(s)) # 속도에 반비례하는 대기 시간(ms)

    start_time = time.ticks_ms()

    try:
        # 테스트를 위해 2초간 구동하는 루프
        while time.ticks_diff(time.ticks_ms(), start_time) < duration_ms:
            now = time.ticks_ms()
            changed = False
            out_bits = 0
            
            for i in range(4):
                if speeds[i] != 0:
                    # 마지막 스텝 이동 후 지정된 인터벌이 지났는지 확인
                    if time.ticks_diff(now, last_step_time[i]) >= intervals[i]:
                        # 스텝 인덱스 업데이트
                        if speeds[i] > 0:
                            current_indices[i] = (current_indices[i] + 1) % 8
                        else:
                            current_indices[i] = (current_indices[i] - 1) % 8
                        
                        last_step_time[i] = now
                        changed = True
                
                # 현재 인덱스의 비트 패턴을 합침 (정지 상태면 마지막 패턴 유지 혹은 0)
                if speeds[i] != 0:
                    out_bits |= (STEP_SEQ[current_indices[i]] << (i * 4))
            
            # 상태가 변했을 때만 I2C 전송 (통신 부하 감소)
            if changed:
                write_pcf8575(out_bits)
            
            # CPU 과열 방지 및 I2C 안정성을 위한 미세 딜레이
            #time.sleep_us(1)
    finally:
        # 테스트 종료 후 또는 에러 발생 시 반드시 모든 모터 정지
        stop_all()            

# 실행 예시: 
motor(-10, 0, 100, 100)


코멘트

“마이크로파이썬으로 구현하는 28BYJ-48 스텝 모터 정밀 구동법” 에 하나의 답글

  1. micro2iot 아바타

    [구매정보] 블로그에서 사용한 부품 구매정보입니다.
    아래 링크를 통해 구매 시 소정의 수수료를 제공받으며, 채널 운영에 큰 도움이 됩니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다