0. 순서

더보기

Step 1. GUI + LED 제어 맛보기

  • 버튼 1개
  • 포트·보드레이트 코드에 고정
  • GUI → 시리얼 → 아두이노 → 로그 흐름 경험 중심

 

Step 2. 포트 선택 추가

  • 포트 선택 콤보박스 추가
  • “어떤 장치와 통신하는가” 개념 학습
  • Step 2는 기능 확장이 아니라, 통신 대상 선택이라는 개념을 추가하는 단계입니다.

 

👉 Step 3. 문자열 송신/수신 추가

  • 자유 문자열 전송 입력창 추가
  • TX / RX 개념 명확화
  • LED 제어 + Echo 응답 확인

 

Step 4. QTimer 수신 처리 추가

  • 주기적 수신 처리 구조 도입
  • GUI 멈춤 현상 체험
  • “왜 비동기가 필요한가” 문제 제기

 

Step 5. QThread 비동기 수신 추가

  • RX 전용 스레드 분리
  • Signal / Slot 구조 학습
  • 안정적인 GUI + 실시간 수신 구현

 

1. 목표

더보기

목표

  • 이 단계의 목표는
    시리얼 통신이 “버튼 전용 제어”가 아니라,
    문자열을 주고받는 통신 구조임을 이해하는 것
    입니다.
  • Step 2까지는 “어떤 장치와 통신하는가” 였다면,
    Step 3에서는 무엇을 보내고, 무엇을 받는가(TX / RX) 를 명확히 인식합니다.

 

학습을 마치면 할 수 있는 것

  • 문자열을 직접 입력하여 아두이노로 전송할 수 있다.
  • 내가 보낸 데이터(TX)와 받은 데이터(RX)를 구분할 수 있다.
  • "1", "0"은 명령,
    "hello" 같은 문자열은 데이터라는 차이를 이해한다.
  • LED 제어와 Echo 응답이 같은 통신 구조임을 이해한다.

 

2. 아두이노 스케치 구현 

: 교재 P.110 ~ 143

더보기
Step 1. 동일

 

3. PySide6 앱 3단계 구현

└─ 3.1 전체 소스 코드

더보기
# Step 3: 문자열 송신/수신 추가
# 목적: TX / RX 개념 명확화 + LED 제어 + Echo 응답 확인

import sys
import serial
from serial.tools import list_ports

from PySide6.QtWidgets import (
    QApplication, QMainWindow, QPushButton,
    QTextEdit, QVBoxLayout, QWidget,
    QComboBox, QLabel, QLineEdit, QHBoxLayout   # [추가]
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Serial LED Test - Step 3")
        self.resize(400, 380)

        self.ser = None
        self.led_on = False

        # --- 포트 선택 UI (Step 2 유지) ---
        self.combo_port = QComboBox()
        self.load_ports()

        # --- [추가] 문자열 송신 UI ---
        self.edit_send = QLineEdit()
        self.edit_send.setPlaceholderText("Send text (ex: hello)")

        self.btn_send = QPushButton("Send")

        # --- 기존 LED 버튼 ---
        self.btn_led = QPushButton("LED OFF")

        self.log = QTextEdit()
        self.log.setReadOnly(True)

        # --- Layout ---
        layout = QVBoxLayout()
        layout.addWidget(QLabel("Port"))
        layout.addWidget(self.combo_port)

        send_row = QHBoxLayout()                 # [추가]
        send_row.addWidget(self.edit_send)
        send_row.addWidget(self.btn_send)

        layout.addLayout(send_row)
        layout.addWidget(self.btn_led)
        layout.addWidget(self.log)

        central = QWidget()
        central.setLayout(layout)
        self.setCentralWidget(central)

        # --- Signals ---
        self.btn_led.clicked.connect(self.toggle_led)
        self.btn_send.clicked.connect(self.send_text)
        self.edit_send.returnPressed.connect(self.send_text)

    # --- 포트 목록 로드 (Step 2 동일) ---
    def load_ports(self):
        ports = list_ports.comports()
        for p in ports:
            self.combo_port.addItem(p.device)

    # --- [공통] 최초 연결 ---
    def ensure_connection(self):
        if self.ser is None:
            port = self.combo_port.currentText()
            if not port:
                self.log.append("No port selected")
                return False
            self.ser = serial.Serial(port, 9600, timeout=1)
            self.log.append(f"Connected to {port}")
        return True

    # --- LED 제어 (기존 + 구조 정리) ---
    def toggle_led(self):
        if not self.ensure_connection():
            return

        self.led_on = not self.led_on
        msg = "1" if self.led_on else "0"
        self.send_and_receive(msg)
        self.btn_led.setText("LED ON" if self.led_on else "LED OFF")

    # --- [추가] 문자열 송신 ---
    def send_text(self):
        if not self.ensure_connection():
            return

        text = self.edit_send.text().strip()
        if not text:
            return
        self.send_and_receive(text)
        self.edit_send.clear()

    # --- [추가] TX / RX 공통 처리 ---
    def send_and_receive(self, text):
        self.ser.write((text + "\n").encode())
        self.log.append(f"TX: {text}")

        line = self.ser.readline().decode().strip()
        if line:
            self.log.append(f"RX: {line}")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())

└─ 3.2 전체 동작 흐름

더보기
[ 포트 선택 ]
        ↓
[ 문자열 입력 또는 LED 버튼 클릭 ] 
                ↓
[ 문자열 전송 (TX) ]
                ↓
[ Arduino 문자열 수신 ]
                ↓
[ LED 제어 또는 Echo 처리 ]
                ↓
[ 응답 문자열 전송 ]
                ↓
[ 로그 창에 RX 표시 ]

 

└─ 3.3 문자열 입력창(QLineEdit) 추가

더보기

소스코드 

self.edit_send = QLineEdit()
self.edit_send.setPlaceholderText("Send text (ex: hello)")

 

의미

  • 버튼 클릭만으로는 “통신”의 본질을 이해하기 어려움
  • 사용자가 직접 문자열을 입력해 전송하도록 확장

학습 포인트

  • 시리얼 통신은 버튼이 아니라 문자열 송신이 본질
  • "1", "0"도 결국 문자열이라는 점을 인식

└─ 3.4 문자열 전송 버튼(Send) 추가

더보기

소스코드

self.btn_send = QPushButton("Send")

 

의미

  • LED 제어 버튼과 자유 문자열 송신을 분리
  • “제어 버튼”과 “데이터 송신”의 역할 차이를 시각화

└─ 3.5 전 UI를 가로 레이아웃으로 구성

더보기
send_row = QHBoxLayout()
send_row.addWidget(self.edit_send)
send_row.addWidget(self.btn_send)

 

의미

  • 입력창 + 버튼을 하나의 “송신 영역”으로 묶음
  • 이후 Step 4, 5 확장 시 구조 유지 용이

└─ 3.6 Signal / Slot 확장

더보기

Send 버튼 클릭 시 문자열 송신

self.btn_send.clicked.connect(self.send_text)

 

Enter 키로도 송신 가능

self.edit_send.returnPressed.connect(self.send_text)

 

의미

  • 실제 터미널/시리얼 모니터 사용 방식과 유사
  • UX 개선이지만, 핵심은 TX 개념 강화

└─ 3.7 공통 연결 처리 함수 분리 (구조 개선)

더보기

ensure_connection() 함수 도입

def ensure_connection(self):
    if self.ser is None:
        port = self.combo_port.currentText()
        if not port:
            self.log.append("No port selected")
            return False
        self.ser = serial.Serial(port, 9600, timeout=1)
        self.log.append(f"Connected to {port}")
    return True

 

의미

  • Step 2에서는 LED 버튼 내부에서 연결 처리
  • Step 3에서는 LED 제어와 문자열 송신이 연결 로직을 공유

학습 포인트

  • 통신 연결은 기능과 분리된 공통 책임
  • 이후 QTimer / QThread로 확장하기 쉬운 구조

└─ 3.8 TX / RX 처리 로직의 명확한 분리

더보기

send_and_receive() 함수 추가

def send_and_receive(self, text):
    self.ser.write((text + "\n").encode())
    self.log.append(f"TX: {text}")

    line = self.ser.readline().decode().strip()
    if line:
        self.log.append(f"RX: {line}")

 

기존(Step 2)

  • LED 버튼 내부에 TX/RX 코드 혼재

현재(Step 3)

  • TX/RX를 하나의 공통 함수로 추상화

학습 포인트

  • TX와 RX는 항상 쌍으로 발생
  • LED 제어와 문자열 송신은 같은 통신 흐름

└─ 3.9 Echo 응답 정리

더보기
PC에서 보낸 TX Arduino 동작 RX 응답
"1" LED ON LED:ON
"0" LED OFF LED:OFF
"hello" LED 변화 없음 ECHO:hello

 

의미

  • 명령과 데이터의 차이를 행동 결과로 체험
  • “문자열을 해석하는 쪽은 Arduino”라는 역할 분리 인식

└─ 3.9 LED 제어 로직의 의미 변화 .

더보기
msg = "1" if self.led_on else "0"
self.send_and_receive(msg)

 

의미 변화

  • LED 버튼 = 하드웨어 제어 버튼 ❌
  • LED 버튼 = "1" 또는 "0" 문자열 송신 버튼 ⭕️

학습 포인트

  • 하드웨어 제어조차 문자 기반 프로토콜의 한 사례
  • 이후 CMD:VALUE 같은 프로토콜로 자연스럽게 확장 가능

└─ 3.10 Echo 응답 정리

더보기
PC에서 보낸 TX Arduino 동작 RX 응답
"1" LED ON LED:ON
"0" LED OFF LED:OFF
"hello" LED 변화 없음 ECHO:hello

 

의미

  • 명령과 데이터의 차이를 행동 결과로 체험
  • “문자열을 해석하는 쪽은 Arduino”라는 역할 분리 인식


4. 테스트

더보기

테스트 1: LED 제어

# 입력:

버튼 클릭
# 로그:

TX: 1
RX: LED:ON

 

테스트 2: 문자열 Echo

# 입력

hello
# 로그

TX: hello
RX: ECHO:hello

 

정상 동작 기준

  • 문자열 입력 시 프로그램이 멈추지 않는다.
  • TX / RX 로그가 명확히 구분된다.
  • LED 제어와 문자열 송신이 모두 정상 동작한다.