기본 코드지식

Seungyun Lee·2026년 4월 25일

Research Assistant

목록 보기
3/6

자동화 제어 파이썬 코드의 기본 구조

import serial
import time

# Initialize serial connections for robot and potentiostat
robot_port = serial.Serial('COM3', baudrate=9600, timeout=1)
potentiostat_port = serial.Serial('COM4', baudrate=115200, timeout=1)

def move_robot(x, y, z):
    # Format command string (e.g., G-code) to send to the robotic arm controller
    command = f"G0 X{x} Y{y} Z{z}\r\n"
    robot_port.write(command.encode('utf-8'))

    # Polling loop: Wait for 'OK' acknowledgment from the robot (similar to checking a valid flag in RTL)
    while True:
        response = robot_port.readline().decode('utf-8').strip()
        if response == "OK":
            break
        time.sleep(0.1)

def run_electrodeposition(voltage, duration):
    # Send command to potentiostat to apply bias
    command = f"SET_V {voltage}\r\nSTART {duration}\r\n"
    potentiostat_port.write(command.encode('utf-8'))
    
    # Wait for the hardware to complete the reaction
    time.sleep(duration)
    
# Main execution loop simulating High-throughput screening
# Array of coordinates corresponding to different beakers (Ternary plot mixtures)
test_coordinates = [(10, 20, 5), (30, 20, 5), (50, 20, 5)]

for target_pos in test_coordinates:
    try:
        # State 1: Move robot arm over the specific beaker
        move_robot(target_pos[0], target_pos[1], target_pos[2])
        
        # State 2: Lower the arm (electrode) into the electrolyte solution
        move_robot(target_pos[0], target_pos[1], target_pos[2] - 10)
        
        # State 3: Trigger the Potentiostat to start coating for 60 seconds
        run_electrodeposition(-1.0, 60)
        
        # State 4: Pull the arm back up
        move_robot(target_pos[0], target_pos[1], target_pos[2])

    except Exception as e:
        # Exception handling is critical to prevent hardware collision or acid spills
        print(f"Hardware error occurred at {target_pos}: {e}")
        # Send emergency stop command
        robot_port.write(b"E-STOP\r\n")
        break

  1. Communication Specs: What are the physical interfaces (UART, TCP/IP) and specific port settings (baud rate, COM port) connecting the main PC to the robot and potentiostat?

  2. API & Manuals: Where can I find the original manufacturer APIs, DLLs, or raw command set manuals (e.g., G-code) for these hardware devices?

  3. Synchronization: How does the Python script verify that a hardware movement or chemical process is finished—does it wait for a hardware ACK signal, or does it just use time.sleep()?

  4. Exception Handling: What is the software-level fail-safe or E-stop routine if a communication timeout or physical collision occurs while the voltage is active?

  5. Environment Setup: How are the Python dependencies managed (e.g., virtual environment, requirements.txt) to ensure this code runs correctly if we need to set up a new PC?


1. "메인 PC와 각 장비(로봇 팔, 전위차계) 간의 물리적 인터페이스와 통신 스펙은 어떻게 됩니까?"

의도: 가장 기본적이지만 놓치기 쉬운 하드웨어 레이어 파악입니다.

세부 질문: "단순 USB-to-Serial(UART) 인가요, 아니면 TCP/IP(이더넷) 통신인가요? 시리얼이라면 포트 번호(COM port) 매칭 규칙이나 통신 속도(Baudrate) 설정값은 어디서 확인합니까?"

2. "각 장비의 원시 명령어 세트(Raw Command Set) 매뉴얼이나 제조사 API 문서는 어디에 저장되어 있습니까?"

의도: 코드에 하드코딩된 f"G0 X{x}" 같은 문자열들이 도대체 어디서 튀어나온 규칙인지 원본 데이터시트를 확보해야 합니다. 하드웨어 개발 시 '레지스터 맵(Register Map)'을 확보하는 것과 똑같습니다.

세부 질문: "로봇 팔 제조사에서 제공하는 G-code 리스트나, 전위차계를 제어하기 위해 불러다 쓰는 DLL/라이브러리 매뉴얼 PDF 파일이 폴더 어디에 있습니까?"

3. "로봇 팔 이동 완료나 도금 공정 종료를 PC가 어떤 방식(타이밍)으로 인지하고 다음 시퀀스로 넘어갑니까?"

의도: 시스템 병목과 딜레이가 가장 많이 터지는 지점인 '동기화(Synchronization)' 방식을 파악합니다.

세부 질문: "로봇 팔이 지정된 위치로 이동할 때까지 파이썬 코드는 그냥 time.sleep()으로 무식하게 기다립니까(Delay 기반), 아니면 로봇 컨트롤러에서 이동이 끝났다는 'ACK(응답)' 신호를 쏴주고 그걸 받아서 넘어갑니까(Polling/Interrupt 기반)?"

4. "장비 간 통신 타임아웃이 발생하거나 충돌(Collision)이 났을 때, 비상 정지(E-Stop)나 복구(Recovery) 루틴은 어떻게 구현되어 있습니까?"

의도: 실제 랩실에서 에러가 터졌을 때 수습하기 위한 안전장치 파악입니다.

세부 질문: "도금 중에 파이썬 통신이 뻗어버리면, 전위차계가 전압 인가를 스스로 멈춥니까 아니면 계속 전류를 쏴서 시료를 태워 먹습니까? 물리적인 비상 정지 스위치 외에 소프트웨어적인 안전망이 코드 어디에 있습니까?"

5. "현재 이 파이썬 코드가 돌아가는 패키지 환경(Dependencies)은 어떻게 관리되고 있습니까?"

의도: 나중에 PC를 포맷하거나 다른 노트북에서 셋업할 때, 버전 충돌로 코드가 아예 안 돌아가는 참사를 막기 위함입니다.

세부 질문: "가상 환경(conda, venv) 세팅이 되어 있습니까? requirements.txt처럼 이 코드를 돌리기 위해 설치해야 하는 특정 버전의 라이브러리 목록이 정리된 파일이 있습니까?"


pyserial 모듈 (시리얼 통신)

import serial
import time

# 1. Initialize UART connection
try:
    # Open COM4 with 115200 baudrate and 2 seconds timeout
    robot_uart = serial.Serial(port='COM4', baudrate=115200, timeout=2.0)
except serial.SerialException:
    # Exit if COM port is already in use or not found
    print("Error: Could not open the serial port.")
    exit()

def send_command_and_wait(command_str):
    # 2. Transmit (TX)
    # Append CR/LF to the command and convert string to bytes
    tx_data = (command_str + "\r\n").encode('utf-8')
    robot_uart.write(tx_data)
    
    # 3. Receive (RX)
    # Wait and read from RX buffer until '\n' is detected or timeout occurs
    rx_bytes = robot_uart.readline()
    
    # Check if timeout occurred (empty byte received)
    if len(rx_bytes) == 0:
        return "TIMEOUT_ERROR"
        
    # Convert received bytes back to string and remove trailing '\r\n'
    response = rx_bytes.decode('utf-8').strip()
    return response

# Execution sequence
# Send a G-code command to move the robotic arm
print("Sending move command...")
result = send_command_and_wait("G0 X10 Y20 Z5")

# Check the ACK signal from the hardware controller
if result == "OK":
    print("Robot reached the target position successfully.")
elif result == "TIMEOUT_ERROR":
    print("Critical: Hardware did not respond within 2 seconds. Check connection.")
else:
    print(f"Hardware returned an unexpected error code: {result}")

# Always close the port when done
robot_uart.close()

serial.Serial()

robot_uart = serial.Serial(port='COM4', baudrate=115200, timeout=2.0)

: 하드웨어 연결 및 UART 세팅
C언어에서 포트를 열고 Baudrate, Parity, Stop bit 레지스터를 세팅하는 과정입니다. 파이썬에서는 객체(Object)를 하나 생성하는 것으로 이 과정을 끝냅니다.

동작: 지정된 COM 포트를 점유하고, 통신 규격을 맞춘 뒤 연결을 엽니다.

  • port: 장치가 연결된 포트 이름 (윈도우는 'COM3', 리눅스는 '/dev/ttyUSB0' 등).

  • baudrate: 통신 속도 (9600, 115200 등). 하드웨어 메뉴얼과 반드시 일치해야 합니다.

  • timeout: (가장 중요) 수신 대기 시간(초)입니다. C언어처럼 무한 while 루프로 RX 버퍼를 폴링(Polling)하면 CPU 점유율이 폭주하므로, 파이썬에서는 응답이 안 오면 일정 시간 후 루프를 빠져나오도록 반드시 타임아웃을 설정해야 합니다.

G0은 항상 고정인가?

G0은 파이썬과 아무런 상관이 없습니다. 프린터, CNC 기계, 랩실용 3축 로봇 팔 등을 제어할 때 전 세계 산업 표준으로 쓰이는 G-코드(G-code)라는 하드웨어 제어 언어(명령어 세트) 중 하나입니다.

G0 (Rapid Move, 급속 이송): * 의미: "경로 신경 쓰지 말고, 모터가 낼 수 있는 최고 속도로 무조건 빨리 그 좌표로 가라."

  • 사용처: 비커와 비커 사이를 허공에서 이동할 때 시간을 단축하기 위해 씁니다.

G1 (Linear Move, 직선 보간 이동):

  • 의미: "내가 지정해 준 속도(Feedrate, F)로 경로를 일정하게 유지하면서 이동해라."
  • 사용처: 촉매가 발린 전극을 산성 용액(비커) 안으로 담글 때 씁니다. 만약 용액에 담글 때 G0을 써버리면, 전극이 용액을 강하게 내리쳐서 비커가 깨지거나 산성 용액이 사방으로 튀는 대형 사고가 납니다.

.write()

# 2. Transmit (TX)
    # Append CR/LF to the command and convert string to bytes
    tx_data = (command_str + "\r\n").encode('utf-8')
    robot_uart.write(tx_data)

: TX (데이터 송신)
PC에서 로봇 팔이나 계측기로 명령(Command)을 쏘는 함수입니다. TX 버퍼에 데이터를 밀어 넣습니다.

  • 주의점 (인코딩): 파이썬의 문자열(String)은 그대로 케이블을 탈 수 없습니다. 반드시 바이트(Byte) 형태로 쪼개서 직렬화해야 합니다.
  • 방법: 전송할 문자열 뒤에 .encode('utf-8')을 붙이거나, 문자열 앞에 b를 붙여야 합니다 (예: b"START").

  • 종결 문자: 하드웨어(장비)가 "아, 명령어가 끝났구나"라고 인식하게 하려면 끝에 항상 캐리지 리턴(\r)이나 라인 피드(\n)를 붙여서 보내야 합니다.

.readline()

# 3. Receive (RX)
    rx_bytes = robot_uart.readline()
    
    response = rx_bytes.decode('utf-8').strip()
    return response

: RX (데이터 수신)
장비에서 PC로 들어온 응답(ACK 신호나 센서 측정값)을 읽어오는 함수입니다. RX 버퍼를 확인합니다.

동작: 수신 버퍼에 쌓인 데이터 중 개행 문자(\n)가 나올 때까지 한 줄을 통째로 읽어옵니다.

주의점 (디코딩 및 클렌징): 들어온 데이터는 바이트 덩어리이므로, 사람이 읽고 변수로 쓰려면 다시 .decode('utf-8')을 통해 문자열로 풀어줘야 합니다.

또한, 하드웨어가 보낸 데이터 끝에는 쓰레기값이나 줄바꿈 문자(\r\n)가 붙어 있으므로, 문자열 앞뒤 공백을 날려버리는 .strip() 함수를 세트로 붙여 쓰는 것이 국룰입니다.

Formatted String (f-string)과 인코딩 (.encode/.decode)

f-string (문자열 포매팅)

: C언어의 sprintf 대체
f는 단순히 string으로 변환한다는 뜻이 아니라, "이 문자열 안에 있는 중괄호 {} 속 변수들을 실제 값으로 치환해서 문자열을 완성해라(Format)"라는 파이썬의 특수 문법입니다.

# Variables representing coordinates
target_x = 100
target_y = 50

# Using f-string to format the command
# This replaces C language's: sprintf(buffer, "G0 X%d Y%d\r\n", target_x, target_y);
command_str = f"G0 X{target_x} Y{target_y}\r\n"

print(command_str) 
# Output: G0 X100 Y50\r\n (A single human-readable string object)

로봇 팔을 제어하려면 X: 10, Y: 20이라는 좌표 변수값을 G0 X10 Y20이라는 하나의 문자열(명령어)로 조립해야 합니다. 과거 파이썬이나 다른 언어에서는 변수와 문자열을 + 기호로 더하거나 복잡한 포맷 기호를 썼지만, 현재는 f-string이 표준입니다.

사용법: 문자열을 여는 따옴표 앞에 소문자 f를 붙이고, 변수를 중괄호 {} 안에 넣으면 끝입니다.

.encode() (송신 - TX): String ➡️ Bytes

# Encode the human-readable string into a byte array
# 'utf-8' translates each character into its corresponding hex/binary value
tx_data = command_str.encode('utf-8')

print(tx_data)
# Output: b'G0 X100 Y50\r\n' (The 'b' prefix indicates it is now raw byte data)

# Send to hardware
# robot_uart.write(tx_data)

앞서 f-string으로 만든 command_str은 파이썬 내부에서만 의미가 있는 '문자열 객체'입니다. 이 객체를 UART 케이블 구리선에 그대로 태워 보낼 수는 없습니다. 반드시 하드웨어가 인식할 수 있는 순수한 8비트 데이터 배열(Raw Bytes)로 쪼개야 합니다.

원리: 문자를 ASCII 코드(또는 UTF-8 규칙) 숫자로 변환하는 과정입니다.

사용법: 문자열 뒤에 .encode('utf-8') 또는 .encode('ascii')를 붙입니다. 산업용 장비 제어에서는 두 방식의 결과가 사실상 동일합니다.

.decode() (수신 - RX)

: Bytes ➡️ String

# Simulated raw bytes received from hardware via serial port
# rx_data = robot_uart.readline()
rx_data = b'OK\r\n' 

# Decode bytes back to string and remove trailing whitespace/newlines
response_str = rx_data.decode('utf-8').strip()

print(response_str)
# Output: OK (Clean string, ready for conditional statements)

if response_str == "OK":
    # Execute next sequence
    pass

로봇 팔이 이동을 완료하고 PC로 "OK"라는 신호를 보냈다고 가정해 보겠습니다. 케이블을 타고 PC로 들어온 데이터 역시 문자열이 아니라 순수한 바이트(Bytes) 배열입니다. 이를 if response == "OK":처럼 조건문에서 비교하려면 다시 사람이 읽을 수 있는 텍스트로 번역해야 합니다.

  • 원리: 수신된 ASCII 코드 숫자 배열을 다시 문자로 조립하는 과정입니다.

  • 사용법: 바이트 데이터 뒤에 .decode('utf-8')을 붙입니다.

  • .strip()의 중요성: 장비가 데이터를 보낼 때 끝에 항상 개행 문자(\r, \n)나 공백이 붙어 옵니다. 이를 그대로 두면 "OK"와 "OK\r\n"이 다르다고 판정되어 코드가 꼬입니다. 따라서 디코드 직후 꼬리표를 잘라내는 .strip()을 무조건 세트로 붙여야 합니다.

데이터의 흐름 (Pipeline):
변수 ➡️ f-string (조립) ➡️ .encode() (바이트 변환) ➡️ UART TX ➡️ 장비 동작 ➡️ UART RX ➡️ .decode() (문자 변환) ➡️ .strip() (쓰레기값 제거) ➡️ 제어 흐름 결정

예외 처리 (try-except 블록):

try-except의 기본 구조와 동작 원리

import serial
import time

robot_port = serial.Serial('COM3', 115200, timeout=1)
potentiostat_port = serial.Serial('COM4', 9600, timeout=1)

def run_experiment(x, y, z):
    # try block: Attempting the risky sequence of operations
    try:
        # 1. Move robot to target position
        move_command = f"G0 X{x} Y{y} Z{z}\r\n".encode('utf-8')
        robot_port.write(move_command)
        
        # 2. Apply voltage for electrodeposition
        potentiostat_port.write(b"SET_V -1.0\r\n")
        time.sleep(10) # Wait for coating process
        
    # except block: Handle specific hardware communication errors (e.g., cable disconnected)
    except serial.SerialException as e:
        print(f"Hardware connection lost: {e}")
        # Send physical Emergency Stop signal if possible
        robot_port.write(b"E-STOP\r\n")
        potentiostat_port.write(b"V_OFF\r\n")
        
    # except block: Catch any other unexpected software errors
    except Exception as e:
        print(f"Unexpected software error occurred: {e}")
        # Ensure voltage is turned off to prevent burning the sample
        potentiostat_port.write(b"V_OFF\r\n")

    # finally block: This runs no matter what (success or failure)
    finally:
        print("Experiment sequence ended. Safely closing ports.")
        # Ensure ports are freed so they can be used again without rebooting the PC
        if robot_port.is_open:
            robot_port.close()
        if potentiostat_port.is_open:
            potentiostat_port.close()

# Start the process
run_experiment(10, 20, 5)

소프트웨어는 실행 중에 통신 케이블이 뽑히거나, 범위를 벗어난 값이 들어오면 즉시 '크래시(Crash)'가 나면서 스크립트가 강제 종료됩니다. 파이썬에서는 이를 '예외(Exception)가 발생했다'고 표현합니다.

try-except는 이 폭탄이 터지는 것을 중간에 가로채서(Catch), 프로그램이 죽는 대신 안전하게 수습할 수 있는 기회를 줍니다.

  • try 블록: "일단 이 코드를 실행해 봐. (하지만 하드웨어 통신이라 에러가 날 확률이 있어.)"

  • except 블록: "만약 위에서 에러가 터지면, 스크립트를 죽이지 말고 여기로 점프해서 수습해!"

  • finally 블록 (옵션): "에러가 나든 안 나든, 맨 마지막에 이건 무조건 실행해. (예: 포트 닫기, 전압 차단)"

Robot Arm Control

1. Safe Z Retraction

가장 빈번하게 발생하는 사고 원인입니다. 로봇 팔이 A 비커에서 B 비커로 이동할 때, 현재 위치에서 목표 위치로 직선(대각선) 이동을 해버리면 비커 벽면을 다 부수고 지나가게 됩니다.

이를 방지하기 위해 코드는 반드시 'ㄷ'자 형태의 시퀀스를 가져야 합니다.

def safe_move_to_beaker(target_x, target_y, target_z):
    # 1. Z-axis Retract: Pull the electrode completely out of the current beaker
    # Moving to a pre-defined 'SAFE_Z' height where there are no obstacles
    send_command(f"G0 Z{SAFE_Z_HEIGHT}")
    wait_for_idle()

    # 2. XY Plane Move: Move laterally to the target beaker's coordinates
    send_command(f"G0 X{target_x} Y{target_y}")
    wait_for_idle()

    # 3. Z-axis Plunge: Lower the electrode into the new solution
    send_command(f"G0 Z{target_z}")
    wait_for_idle()

2. 절대 좌표 vs 상대 좌표 (Absolute vs. Relative Positioning)

모터 제어 명령어가 절대 좌표(Absolute, G90) 기준인지, 상대 좌표(Relative, G91) 기준인지 명확히 파악해야 합니다.
마이크로프로세서에서 메모리 '절대 주소'에 값을 쓸 것인지, 현재 포인터 위치에서 '오프셋(Offset)'만큼 이동할 것인지의 차이와 같습니다.

절대 좌표 (예: X=10): 현재 위치가 어디든 상관없이, 작업대(Table)의 기준점으로부터 10cm 떨어진 고정 위치로 이동합니다. (랩실 자동화에서 주로 쓰임)

상대 좌표 (예: X=+10): 현재 로봇 팔이 있는 위치에서 우측으로 10cm 더 이동합니다.

내일 확인할 것: 코드가 어떤 모드로 세팅되어 돌아가는지 확인해야 합니다. 상대 좌표 모드에서 코드를 잘못 짜면 루프를 돌 때마다 위치가 누적되어 로봇 팔이 작업대 밖으로 탈선하게 됩니다.

3. 호밍과 원점 복귀 (Homing / Calibration)

로봇 팔의 전원을 방금 켰다고 가정해 보겠습니다. 컨트롤러는 자기 자신이 3차원 공간의 어디에 있는지 알지 못합니다.
디지털 회로에서 전원을 켜자마자 플립플롭들을 0으로 초기화하기 위해 리셋(Reset) 신호를 때리는 것과 완벽히 같은 개념입니다.

동작 원리: 로봇 팔이 각 축(X, Y, Z)의 끝에 달린 물리적인 리미트 스위치(Limit Switch)를 '딸깍'하고 칠 때까지 천천히 이동합니다. 스위치가 눌리는 순간을 공간의 원점(0, 0, 0)으로 인식하고 내부 카운터를 초기화합니다.

내일 확인할 것: "파이썬 스크립트를 처음 실행할 때, 장비가 자동으로 호밍(Homing) 루틴을 수행하는지, 아니면 수동으로 특정 명령을 먼저 쏴줘야 하는지"를 물어봐야 합니다

4. 소프트웨어 리미트 (Software Limits / Bounding Box)

물리적인 스위치 외에도, 파이썬 코드 단에서 로봇이 지정된 작업 공간(Workspace) 밖으로 나가지 못하게 막는 '안전 울타리(Bounding Box)'가 있어야 합니다.

# Hardware workspace physical limits defined in millimeters
MAX_X = 500
MAX_Y = 300
MIN_Z = 0

def move_robot(x, y, z):
    # Software limit check before sending the command to the hardware
    if x > MAX_X or y > MAX_Y or z < MIN_Z:
        print("Error: Target coordinates are outside the safe bounding box!")
        return False # Cancel the movement
        
    # Proceed with sending UART command if safe
    send_command(f"G0 X{x} Y{y} Z{z}")
profile
Design Verification engineer

0개의 댓글