Serial 통신이란?

Eden.Yang·2025년 3월 17일

https://m.blog.naver.com/ansdbtls4067/220886156177

Serial 통신과 Parallel 통신의 모식도

병렬과 직렬의 차이점은 위와 같다. 직렬은 하나의 통신선만 사용하기에 저렴하다. USB, PC COM port등이 있다.

동기 시리얼 통신과 비동기 시리얼 통신의 차이

동기 시리얼 통신은 별도의 통신을 수행하는 동기화 Clock선이 존재한다. 따라서 오류가 적고 빠른 데이터 통신이 가능하지만 통신비용이 증가되고 전자회로가 다소 복잡해진다.

비동기 통신의 경우 통신선과 제어선만 구성되어 2진 데이터 신호가 통신 회선을 통해 이동하는 과정 중 원본 데이터에 대한 2진 데이터 신호가 사라지거나 동기 시리얼 통신에 비해 전송할 데이터의 처음과 끝에 각종 통신 제어관련 정보를 함께 실어 보내야 한다. 따라서 속도가 비교적 느리다.


Grok의 serial 통신 설명
시리얼 통신(Serial Communication)은 데이터를 한 번에 한 비트씩 순차적으로 전송하는 통신 방식입니다. 병렬 통신(Parallel Communication)이 데이터를 여러 비트를 동시에 전송하는 것과 달리, 시리얼 통신은 데이터를 일렬로 나열해 시간 순서대로 보내는 방식이라 전선 수가 적어도 되며, 장거리 통신이나 간단한 장치 간 연결에 유리합니다.

시리얼 통신의 주요 특징

  1. 데이터 전송 방식:

    • 비트 단위로 순차 전송.
    • 시작 비트(Start Bit)와 종료 비트(Stop Bit)를 사용해 데이터의 시작과 끝을 알림.
    • 예: "10110010"라는 바이트를 보내면, 한 비트씩 차례로 전송.
  2. 동기 vs 비동기:

    • 동기 통신: 송신 측과 수신 측이 클럭 신호를 공유해 데이터 전송 타이밍을 맞춤 (예: SPI, I2C).
    • 비동기 통신: 클럭 없이 시작/종료 비트로 동기화하며, 수신 측이 미리 정해진 속도(보드레이트)로 데이터를 읽음 (예: UART).
  3. 보드레이트(Baud Rate):

    • 초당 전송되는 비트 수를 의미 (bps, bits per second).
    • 예: 9600bps는 초당 9600비트를 보냄.
    • 송신자와 수신자가 동일한 보드레이트를 사용해야 데이터가 제대로 전달됨.
  4. 전송 방향:

    • 단방향(Simplex): 한 방향으로만 데이터 전송 (예: 키보드 → 컴퓨터).
    • 반이중(Half-Duplex): 양방향이지만 동시에 전송 불가 (예: 워키토키).
    • 전이중(Full-Duplex): 양방향으로 동시에 전송 가능 (예: 전화).

시리얼 통신의 예시

  • UART(Universal Asynchronous Receiver/Transmitter): 가장 흔한 비동기 시리얼 통신 방식. 컴퓨터와 마이크로컨트롤러 간 통신에 자주 사용.
  • RS-232: 과거 컴퓨터와 모뎀 연결에 널리 사용된 표준.
  • USB: 현대적인 시리얼 통신 방식으로, 속도와 편의성이 개선됨.
  • SPI/I2C: 주로 센서나 디바이스 간 짧은 거리 통신에 사용.

장단점

  • 장점:
    • 배선이 단순하고 비용이 저렴.
    • 장거리 통신 가능 (특히 RS-232, RS-485 같은 표준).
  • 단점:
    • 병렬 통신보다 속도가 느릴 수 있음.
    • 비동기 방식은 타이밍 오류가 발생할 가능성 있음.

실제 내가 프로젝트에 적용한 python 코드

1. 모듈 임포트

from serial import Serial
from serial.tools.list_ports import comports
  • Serial: pyserial 라이브러리의 핵심 클래스. 시리얼 포트를 열고 데이터를 읽거나 쓰는 데 사용됩니다.
  • comports: 사용 가능한 COM 포트 목록을 가져오는 유틸리티 함수. GUI에서 포트 선택 드롭다운을 채우는 데 활용됩니다.

2. 초기화 및 변수 설정

class SerialReaderGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        # ... (다른 GUI 초기화 코드 생략) ...

        self.ser = None  # 시리얼 객체 초기화
        self.is_connected = False  # 연결 상태 플래그
  • self.ser: 시리얼 통신 객체를 저장하는 변수로, 초기에는 None으로 설정됩니다. 이후 연결 시 Serial 객체로 대체됩니다.
  • self.is_connected: 시리얼 포트 연결 여부를 추적하는 플래그.

3. COM 포트 목록 업데이트

def update_ports(self):
    self.port_combo.clear()
    ports = [port.device for port in comports()]  # 사용 가능한 포트 목록 가져오기
    if ports:
        self.port_combo.addItems(ports)
        self.log_message(f"포트 목록 업데이트: {', '.join(ports)}")
    else:
        self.log_message("사용 가능한 COM 포트가 없습니다.")
  • comports(): 시스템에서 사용 가능한 모든 시리얼 포트를 반환합니다. 각 포트는 port.device 속성으로 이름(예: 'COM3', '/dev/ttyUSB0')을 제공합니다.
  • 기능: GUI의 QComboBox에 포트 목록을 채우고, 로그에 업데이트된 포트 정보를 기록합니다.

4. 시리얼 연결 시작

def connect_serial(self):
    try:
        port = self.port_combo.currentText()  # 선택된 포트 가져오기
        self.ser = Serial(port=port, baudrate=9600, timeout=1)  # 시리얼 객체 생성
        self.is_connected = True
        self.connect_button.setText('연결 해제')
        self.status_label.setText(f'상태: {port}에 연결됨')
        self.log_message(f'연결 성공: {port}')
        self.timer.start()  # 데이터 읽기 타이머 시작
    except Exception as e:
        self.log_message(f'연결 실패: {str(e)}')
  • Serial(port, baudrate, timeout): 시리얼 포트를 엽니다.
    • port: 선택된 COM 포트 (예: 'COM3').
    • baudrate=9600: 데이터 전송 속도. 수분 측정기가 9600bps로 설정되어 있다고 가정.
    • timeout=1: 읽기 타임아웃을 1초로 설정. 데이터가 없으면 1초 후 읽기를 종료.
  • 연결 성공 시:
    • self.ser에 시리얼 객체 저장.
    • 연결 상태 업데이트 및 GUI 버튼/상태 라벨 변경.
    • self.timer.start()로 주기적 데이터 읽기 시작.
  • 예외 처리: 연결 실패 시 오류 메시지를 로그에 기록.

5. 시리얼 연결 해제

def disconnect_serial(self):
    if self.ser and self.ser.is_open:  # 시리얼 객체가 존재하고 열려 있는지 확인
        self.timer.stop()  # 데이터 읽기 타이머 중지
        self.ser.close()  # 시리얼 포트 닫기
    self.is_connected = False
    self.connect_button.setText('연결')
    self.status_label.setText('상태: 연결 안됨')
    self.log_message('연결 종료')
  • self.ser.is_open: 포트가 열려 있는지 확인.
  • self.ser.close(): 열려 있는 시리얼 포트를 닫음.
  • 기능: 연결을 종료하고 GUI 상태를 업데이트하며, 타이머를 멈춰 더 이상 데이터를 읽지 않도록 함.

6. 데이터 읽기

def read_data(self):
    if not self.ser or not self.ser.is_open:  # 시리얼 객체가 없거나 닫혀 있으면 종료
        return
    
    try:
        if self.ser.in_waiting:  # 수신 버퍼에 데이터가 있는지 확인
            data = self.ser.readline().decode('ascii').strip()  # 데이터 읽기
            if data:
                self.log_message(f'수신: {data}')
                if 'Final Result' in data:
                    # "Final Result" 뒤의 숫자 추출
                    match = re.search(r'Final Result[.\s]+([\d.]+)', data)
                    if match:
                        result_value = match.group(1)
                        self.log_message(f'클립보드에 복사됨: {result_value}')
                        pyperclip.copy(result_value)  # 클립보드에 복사
                        self.log_message(f'커서에 붙여넣기 시작: {result_value}')
                        pyautogui.write(result_value, interval=0.01)  # 현재 커서에 입력
                        pyautogui.press('enter')  # Enter 키 입력
                        self.log_message(f'붙여넣기 완료: {result_value}')
                    else:
                        self.log_message('숫자 추출 실패')
    except Exception as e:
        self.log_message(f'데이터 읽기 오류: {str(e)}')
  • self.ser.in_waiting: 수신 버퍼에 읽을 수 있는 바이트 수가 있는지 확인. 0이면 데이터가 없음.
  • self.ser.readline(): 한 줄(개행 문자 \n까지)을 읽음. 타임아웃이 설정되어 있으므로 데이터가 없으면 1초 후 빈 값을 반환.
  • decode('ascii'): 바이트 데이터를 ASCII 문자열로 변환. 수분 측정기가 다른 인코딩(예: UTF-8)을 사용한다면 수정 필요.
  • strip(): 문자열 양쪽의 공백/개행 문자 제거.
  • 데이터 처리:
    • 수신된 데이터를 로그에 기록.
    • 데이터에 'Final Result'가 포함되면, 정규 표현식(re.search)으로 숫자(소수점 포함)를 추출.
    • 추출된 숫자를 클립보드에 복사(pyperclip.copy)하고, 현재 커서 위치에 입력(pyautogui.write) 후 Enter 키 입력.
  • 예외 처리: 읽기나 파싱 중 오류 발생 시 로그에 기록.

7. 창 닫기 시 시리얼 연결 정리

def closeEvent(self, event):
    self.disconnect_serial()  # 창을 닫을 때 시리얼 연결 해제
    event.accept()
  • closeEvent: PyQt5에서 창이 닫힐 때 호출되는 메서드.
  • 기능: 프로그램 종료 시 시리얼 포트가 열려 있으면 닫아 자원 누수를 방지.

요약: 시리얼 통신의 전체 흐름

  1. 포트 탐색: comports()로 사용 가능한 포트를 GUI에 표시.
  2. 연결: Serial 객체를 생성해 선택한 포트(baudrate=9600)에 연결.
  3. 데이터 읽기: 50ms 간격(QTimer)으로 readline()을 호출해 데이터를 주기적으로 읽음.
  4. 데이터 처리: 'Final Result'가 포함된 데이터를 파싱해 숫자를 추출하고 클립보드 및 커서에 입력.
  5. 연결 해제: 버튼 클릭 또는 프로그램 종료 시 포트를 안전하게 닫음.

추가 팁

  • 보드레이트 확인: 수분 측정기가 9600이 아닌 다른 속도를 사용한다면(예: 115200), baudrate 값을 조정해야 합니다.
  • 데이터 형식: 측정기가 'Final Result 12.34' 외에 다른 형식으로 데이터를 보낸다면, 정규 표현식(re.search)을 수정해야 할 수 있습니다.
  • 타이머 간격: self.timer.setInterval(50)은 50ms마다 읽기를 시도하는데, 데이터 전송 주기가 느리다면 간격을 늘릴 수 있습니다.
profile
손끝에서 땅끝으로, 골방에서 열방으로

0개의 댓글