Step 4. QTimer 수신 처리

0. 순서
더보기
Step 1. GUI + LED 제어 맛보기
- 버튼 1개
- 포트·보드레이트 코드에 고정
- GUI → 시리얼 → 아두이노 → 로그 흐름 경험 중심
Step 2. 포트 선택 추가
- 포트 선택 콤보박스 추가
- “어떤 장치와 통신하는가” 개념 학습
- Step 2는 기능 확장이 아니라, 통신 대상 선택이라는 개념을 추가하는 단계입니다.
Step 3. 문자열 송신/수신 추가
- 자유 문자열 전송 입력창 추가
- TX / RX 개념 명확화
- LED 제어 + Echo 응답 확인
👉 Step 4. QTimer 수신 처리 추가
- 주기적 수신 처리 구조 도입
- GUI 멈춤 현상 체험
- “왜 비동기가 필요한가” 문제 제기
Step 4는 해결 단계가 아니라, 문제를 “느끼게 만드는 단계”입니다.
Step 5. QThread 비동기 수신 추가
- RX 전용 스레드 분리
- Signal / Slot 구조 학습
- 안정적인 GUI + 실시간 수신 구현
1. 목표
더보기


목표
- 이 단계의 목표는
시리얼 수신을 “동기적으로 처리하면 어떤 문제가 발생하는지”를 직접 체험하는 것입니다. - Step 3까지는 TX와 RX의 개념을 이해하는 단계 였다면,
Step 4에서는 RX를 언제, 어떻게 처리해야 하는가? 라는 질문을 스스로 하게 합니다.
학습을 마치면 할 수 있는 것
- QTimer를 사용해 주기적으로 시리얼 수신을 시도할 수 있다.
- RX 처리 방식에 따라 GUI 응답성이 달라진다는 사실을 체험한다.
- GUI가 멈추는 현상이 통신 속도 문제가 아님을 이해한다.
- “왜 비동기 처리가 필요한가?”라는 질문을 스스로 제기할 수 있다.
2. 아두이노 스케치 구현
: 교재 P.110 ~ 143
더보기
Step 1. 동일
3. PySide6 앱 4단계 구현
└─ 3.1 전체 소스 코드
더보기
핵심 변화 요약
| 구분 | Step 3 | Step 4 |
| RX 처리 | 송신 직후 즉시 수신 | 주기적으로 수신 |
| 처리 방식 | 동기(blocking) | 타이머 기반 |
| 목적 | 개념 이해 | 문제 체험 |
# Step 4: QTimer 기반 수신 처리
# 목적: 주기적 RX 처리 + GUI 멈춤 현상 체험
import sys
import serial
from serial.tools import list_ports
from PySide6.QtCore import QTimer # [추가]
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 4")
self.resize(420, 420)
self.ser = None
self.led_on = False
# --- 포트 선택 ---
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)
# --- [추가] QTimer 설정 ---
self.timer = QTimer(self)
self.timer.timeout.connect(self.read_serial)
self.timer.start(50) # 50ms 주기
def load_ports(self):
for p in list_ports.comports():
self.combo_port.addItem(p.device)
def ensure_connection(self):
if self.ser is None:
port = self.combo_port.currentText()
if not port:
return False
self.ser = serial.Serial(port, 9600, timeout=0.1)
self.log.append(f"Connected to {port}")
return True
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(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(text)
self.edit_send.clear()
def send(self, text):
self.ser.write((text + "\n").encode())
self.log.append(f"TX: {text}")
# --- [핵심] 주기적 RX 처리 ---
def read_serial(self):
if self.ser and self.ser.in_waiting:
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 전체 동작 흐름
더보기
[ 문자열 전송 (TX) ]
↓
[ Arduino 처리 ]
↓
[ 응답 대기 ]
↓
[ QTimer 주기적 호출 ]
↓
[ RX 읽기 시도 ]
↓
[ 로그 창에 표시 ]
└─ 3.3 QTimer 도입
더보기
소스코드
self.timer = QTimer(self)
self.timer.timeout.connect(self.read_serial)
self.timer.start(50)
의미
- RX를 버튼이나 송신 함수 안에서 처리하지 않음
- 일정 주기로 “받을 게 있는지 확인”
- GUI 이벤트 루프 안에서 실행됨
└─ 3.4 GUI 멈춤 현상 체험
더보기
관찰되는 현상
- 수신 주기를 짧게 하면
→ GUI가 느려짐 - 수신 처리에 시간이 걸리면
→ 버튼 반응이 늦어짐
중요한 사실
- 통신 속도가 느린 것이 아니다
- RX 처리가 GUI 이벤트 루프를 점유하고 있기 때문이다
문제 파악 및 개선 사항
- 이 시점에서 자연스럽게 질문이 생깁니다.
- “수신을 GUI와 분리할 수는 없을까?”
Step 4의 핵심
- 왜 수신 처리를 버튼 함수에서 빼야 할까?
- 왜 QTimer를 써도 완벽하지 않을까?
- GUI와 통신은 같은 흐름에서 처리해도 될까?
4. 테스트
더보기
테스트 1: 기본 동작
- 문자열 송신 정상
- RX 로그 정상 출력
테스트 2: GUI 반응성 관찰
- RX 주기를 10ms로 줄여본다
- 문자열을 빠르게 연속 송신한다
- 버튼 클릭 반응을 관찰한다
정상 동작 기준
- 프로그램이 크래시되지 않는다
- RX 로그는 출력되지만
- GUI 반응이 불안정해질 수 있다
👉 이 불안정함이 의도된 학습 포인트입니다.