목차
1. 왜 파이썬으로 시리얼 통신을 할까?
아두이노, 라즈베리파이, 각종 산업용 장비 등 많은 하드웨어는 시리얼 통신(Serial Communication)을 통해 외부와 데이터를 주고받습니다. 파이썬은 쉽고 강력한 pyserial 라이브러리를 제공하여 이러한 장치들과 손쉽게 통신할 수 있는 환경을 제공합니다.
단순한 텍스트를 넘어 온도, 습도 같은 센서 값이나 모터 제어 명령 등 정수, 실수 형태의 데이터를 주고받을 때는 바이너리(binary) 형식으로 변환하는 과정이 필요합니다. 이때 struct 모듈이 빛을 발합니다. 이 두 가지를 조합하면 매우 안정적이고 효율적인 데이터 통신이 가능합니다.
2. pyserial - 시리얼 통신의 시작
pyserial은 파이썬에서 시리얼 포트(COM 포트)를 제어하기 위한 표준 라이브러리입니다.
2.1. 설치 및 포트 확인
먼저 pip을 이용해 pyserial을 설치합니다.
pip install pyserial
설치 후, 장치가 어떤 COM 포트에 연결되었는지 확인해야 합니다.
- Windows: 장치 관리자 → 포트 (COM & LPT) 에서 확인 (예: `COM3`)
- Linux: 터미널에서 `ls /dev/tty*` 명령어로 확인 (예: `/dev/ttyUSB0` 또는 `/dev/ttyACM0`)
- macOS: 터미널에서 `ls /dev/cu.*` 명령어로 확인 (예: `/dev/cu.usbmodem1411`)
2.2. 기본 사용법: 연결, 송신, 수신
기본적인 사용법은 매우 간단합니다. `serial.Serial` 객체를 생성하고, `write()`와 `read()` 메서드를 사용하면 됩니다.
import serial
import time
# 시리얼 포트와 보드레이트를 설정하여 객체 생성
# 포트 이름은 실제 연결된 포트에 맞게 수정해야 함
# 보드레이트(baudrate)는 장치와 동일하게 설정해야 함
ser = serial.Serial('COM3', 9600, timeout=1)
# 데이터 보내기 (bytes 형태로 인코딩 필요)
ser.write(b'Hello Arduino\n')
# 잠시 대기
time.sleep(1)
# 데이터 읽기
if ser.in_waiting > 0:
# readline()은 \n을 만날 때까지 읽음
line = ser.readline().decode('utf-8').rstrip()
print(line)
# 포트 닫기
ser.close()
`write()`는 바이트(bytes)를 보냅니다!
파이썬3부터 문자열과 바이트가 명확히 구분됩니다. `ser.write()`로 데이터를 보낼 때는 반드시 바이트 형태로 보내야 합니다. 문자열 앞에 `b`를 붙이거나 (`b'hello'`), `.encode()` 메서드를 사용 (`'hello'.encode('utf-8')`)하여 바이트로 변환할 수 있습니다.
2.3. 간단한 예제: 아두이노와 문자열 주고받기
파이썬에서 '1'을 보내면 LED를 켜고, '0'을 보내면 끄는 간단한 예제입니다.
아두이노 코드
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
}
void loop() {
if (Serial.available() > 0) {
char data = Serial.read();
if (data == '1') {
digitalWrite(13, HIGH);
Serial.println("LED ON");
} else if (data == '0') {
digitalWrite(13, LOW);
Serial.println("LED OFF");
}
}
}
파이썬 코드
import serial
ser = serial.Serial('COM3', 9600)
ser.write(b'1') # LED 켜기
response = ser.readline().decode().strip()
print(f"Arduino says: {response}")
ser.close()
3. struct - 바이너리 데이터 다루기
3.1. 왜 문자열이 아닌 바이너리 데이터를 사용할까?
문자열 "3.14"는 4바이트를 차지하지만, 실제 4바이트 실수(float)는 훨씬 더 정밀한 값을 표현할 수 있습니다. 여러 센서 데이터를 보낼 때 "temp:25.5,humi:60.2"처럼 문자열로 보내면 파싱(parsing) 과정이 번거롭고 데이터 크기도 커집니다. `struct`를 사용해 정수, 실수 등을 정해진 규격의 바이트 덩어리(struct)로 만들어 보내면, 파싱 없이 바로 데이터로 변환할 수 있어 효율적입니다.
3.2. 핵심 함수: `pack`과 `unpack`
`struct` 모듈의 핵심은 두 함수입니다.
struct.pack(format, v1, v2, ...): 주어진 포맷에 따라 값들을 바이트 객체로 압축(packing)합니다.struct.unpack(format, buffer): 바이트 객체를 주어진 포맷에 따라 값들의 튜플로 해제(unpacking)합니다.
3.3. 데이터 구조를 정의하는 '포맷 문자열'
`pack`과 `unpack`의 첫 인자인 포맷 문자열은 데이터의 구조, 순서, 크기를 정의합니다.
| 문자 | C 타입 | 파이썬 타입 | 크기(byte) |
|---|---|---|---|
< |
리틀 엔디안 (Little-endian) | - | |
B |
unsigned char | integer | 1 |
h |
short | integer | 2 |
i |
int | integer | 4 |
f |
float | float | 4 |
예를 들어, 포맷 '<Bf'는 '리틀 엔디안 방식으로, 1바이트 부호 없는 정수(B) 1개와 4바이트 실수(f) 1개가 순서대로 있다'는 의미입니다.
4. 실전 예제: `pyserial`과 `struct`로 센서 데이터 받기
아두이노가 LED 상태(0 또는 1)와 가상의 온도 값(실수)을 `struct`를 이용해 파이썬으로 보내는 예제입니다.
4.1. 아두이노 코드: 정수와 실수를 struct로 전송
// 데이터 구조체 정의
struct SensorData {
byte ledState; // 1바이트 정수
float temperature; // 4바이트 실수
};
SensorData data;
void setup() {
Serial.begin(9600);
data.ledState = 1; // 초기 LED 상태
}
void loop() {
// 가상의 온도 값 생성
data.temperature = 20.0 + (millis() % 1000) / 100.0;
// 구조체를 바이트 배열로 전송
Serial.write((byte*)&data, sizeof(data));
delay(1000);
}
4.2. 파이썬 코드: 수신 후 struct로 해석
import serial
import struct
# 포맷 문자열 (아두이노 struct와 일치해야 함)
# < : Little-endian
# B : unsigned char (1 byte)
# f : float (4 bytes)
data_format = '<Bf'
data_size = struct.calcsize(data_format) # 포맷의 총 바이트 크기 계산 (5)
ser = serial.Serial('COM3', 9600)
print(f"Waiting for {data_size} bytes of data...")
while True:
if ser.in_waiting >= data_size:
# 정확히 필요한 바이트 수만큼 읽기
packet = ser.read(data_size)
# unpack을 사용하여 바이트를 데이터로 변환
# 결과는 튜플로 반환됨
unpacked_data = struct.unpack(data_format, packet)
led_state = unpacked_data[0]
temperature = unpacked_data[1]
print(f"Received -> LED State: {led_state}, Temperature: {temperature:.2f} C")
ser.close() # 실제로는 루프를 빠져나올 로직이 필요
`struct.calcsize()`의 중요성
데이터를 읽을 때 `ser.read(struct.calcsize(format))` 처럼 정확한 크기를 지정하면, 데이터가 깨지거나 동기화가 맞지 않는 문제를 예방할 수 있습니다. 통신 프로토콜을 설계할 때 매우 유용한 함수입니다.
5. 결론
파이썬으로 하드웨어와 시리얼 통신을 할 때 `pyserial`은 통신 채널을, `struct`는 데이터의 형식을 담당하는 환상의 조합입니다.
- 간단한 제어 신호나 텍스트는 `pyserial`만으로 충분합니다.
- 여러 종류의 센서 데이터나 수치 데이터를 주고받을 때는 `struct`를 함께 사용하면 훨씬 간결하고 안정적인 코드를 작성할 수 있습니다.
이 가이드를 바탕으로 여러분의 파이썬 프로젝트와 다양한 하드웨어를 성공적으로 연결해보시길 바랍니다.
#파이썬 #pyserial #struct #시리얼통신 #아두이노 #PythonSerial #BinaryData
'파이썬 > Basic' 카테고리의 다른 글
| 딕셔너리 빈 값 제거 완전 가이드: 깔끔한 데이터 정리 기법 (2) | 2025.06.04 |
|---|---|
| Python의 UnicodeDecodeError 해결하기: cp949 코덱 오류 (0) | 2025.05.09 |
| Python JSON 라이브러리 비교: json vs ujson vs orjson (0) | 2025.02.26 |
| Geopy : 지리 정보 다루기 (2) | 2025.02.10 |
| lru_cache : 캐싱 메모리 (0) | 2025.02.08 |