Raspberry Pi 연동

이 글에서는

GrowSpace UWB 개발자 태그를 Raspberry Pi 4 Model B와 연결하여, lec, lep 명령어를 통해 실시간 위치 데이터를 수신하고 파싱하는 방법을 안내합니다. Python 환경에서 시리얼 통신을 구현하고, 위치 데이터를 직접 분석하는 실습 예제를 중심으로 구성되어 있습니다.

준비물

  • Raspberry Pi 4 Model B

  • USB-C 전원 어댑터 (5V 3A 이상)

  • GrowSpace UWB 개발자 태그

  • 점퍼 케이블 (TX, RX, GND, 3.3V)

  • Python 3.9.2 이상 (OS: Debian GNU/Linux 11 기준)

  • pyserial 패키지 설치


Raspberry Pi 시리얼 포트 설정

  1. 터미널에 다음 명령어 입력:

    sudo raspi-config
  2. 메뉴 이동:

    • Interface Options → Serial Port

    • Login shell via serial: No

    • Enable serial port hardware: Yes

  3. 설정 완료 후 sudo reboot 명령어로 재부팅


시리얼 핀 연결 (UART)

GrowSpace 개발자 태그의 3.3V 커넥터(좌측) 기준으로 다음과 같이 연결합니다:

개발자 태그 핀

라즈베리파이 핀 번호

GPIO 번호

TX

10번 (RXD)

GPIO15

RX

8번 (TXD)

GPIO14

3.3V

1번

-

GND

6번

-

⚠️ 반드시 TX ↔ RX 교차 연결을 확인해 주세요. 태그의 TX → Pi의 RX, 태그의 RX → Pi의 TX

참고: Raspberry Pi GPIO 핀 배치도

아래는 Raspberry Pi 40핀 헤더의 전체 핀맵입니다. 다음 핀 번호를 기준으로 정확히 연결하세요:

  • TX0: GPIO14 (Pin 8)

  • RX0: GPIO15 (Pin 10)

  • 3.3V: Pin 1

  • GND: Pin 6

이 다이어그램을 참고하면 개발자 태그와의 시리얼 연결을 더욱 명확히 이해할 수 있습니다.

공식 문서에서 전체 GPIO 구성도와 회로 정보를 자세히 보고 싶다면 다음 링크를 확인해 주세요: 라즈베리 파이 공식 핀아웃 및 도면 보기


주의사항: GrowSpace 태그의 시리얼 포트 구분

GrowSpace 개발자 태그에는 좌우 측에 각각 다른 전압 레벨의 시리얼 포트가 존재합니다. 단순한 핀 위치 차이가 아니라, 전기적으로 완전히 다른 레벨이므로 반드시 구분하여 사용해야 합니다.

  • 포트 1 (좌측 커넥터)

    • 구성: TX, RX, 3.3V, GND

    • 동작 전압: 3.3V 레벨

    • 사용 용도: Raspberry Pi, ESP32 등 3.3V 기반 장치와 연결 시 사용

  • 포트 2 (우측 커넥터)

    • 구성: TX, RX, 5V, GND

    • 동작 전압: 5V 레벨

    • 사용 용도: Arduino UNO 등 5V 기반 장치와 연결 시 사용

반드시 확인하세요

  • Raspberry Pi의 GPIO는 3.3V 전압만 허용합니다.

  • 5V 포트(우측 커넥터)를 잘못 연결하면 라즈베리파이의 UART 회로가 손상될 수 있습니다.

  • 본 매뉴얼에서는 반드시 **좌측 커넥터(3.3V 포트)**를 사용해야 합니다.


Python 기반 시리얼 통신 코드

import serial
import threading

uwb = serial.Serial('/dev/serial0', baudrate=115200, timeout=0.5)

def read_from_uwb():
    while True:
        if uwb.in_waiting:
            data = uwb.readline().decode(errors='ignore').strip()
            if data:
                print(f"[UWB 응답] {data}")

def write_to_uwb():
    while True:
        try:
            cmd = input(">>> ")
            if cmd.strip():
                uwb.write((cmd + '\r').encode())
        except KeyboardInterrupt:
            print("\n종료합니다.")
            break

if __name__ == "__main__":
    print("UWB 시리얼 중계 시작 (/dev/serial0)")
    threading.Thread(target=read_from_uwb, daemon=True).start()
    write_to_uwb()
  • read_from_uwb()는 실시간 수신 데이터를 출력

  • write_to_uwb()는 명령어를 입력 받아 전송 (\r 포함)

예: si 명령어 입력 시, 장치 정보가 출력되면 통신 성공


위치 데이터 파싱 예제 (lep, lec 명령어)

LEP (위치 데이터 전용)

def parse_lep(line):
    print("\n[LEP 위치 결과]")
    parts = line.strip().split(',')
    if len(parts) >= 5:
        print(f"X: {parts[1]}")
        print(f"Y: {parts[2]}")
        print(f"Z: {parts[3]}")
        print(f"품질(QF): {parts[4]}")
    else:
        print("→ 잘못된 LEP 형식입니다.")

LEC (거리 + 위치 정보)

def parse_lec(line):
    print("\n[LEC 거리 + 위치 결과]")
    try:
        pos_idx = line.index("POS,")
        dist_part = line[:pos_idx].strip()
        pos_part = line[pos_idx:].strip()

        anchors = []
        tokens = dist_part.split(',')
        i = 2
        while i < len(tokens):
            if tokens[i].startswith("AN"):
                anchor_id = tokens[i+1]
                x = float(tokens[i+2])
                y = float(tokens[i+3])
                z = float(tokens[i+4])
                d = float(tokens[i+5])
                anchors.append((anchor_id, x, y, z, d))
                i += 6
            else:
                i += 1

        for idx, (aid, x, y, z, d) in enumerate(anchors):
            print(f"AN{idx} (ID {aid}): x={x}, y={y}, z={z}, 거리={d}m")

        parse_lep(pos_part)

    except Exception as e:
        print(f"lec 파싱 실패: {e}")

전체 수신 루프 구성 (자동 파싱)

input_buffer = ""

def read_from_uwb():
    global input_buffer
    while True:
        if uwb.in_waiting:
            data = uwb.read().decode(errors='ignore')
            if data == '\n':
                line = input_buffer.strip()
                if line.startswith("POS,"):
                    parse_lep(line)
                elif line.startswith("DIST,"):
                    parse_lec(line)
                else:
                    print(f"[기타 응답] {line}")
                input_buffer = ""
            else:
                input_buffer += data
  • POS,로 시작하면 LEP 파싱

  • DIST,로 시작하면 LEC 파싱

  • 기타 응답도 모두 출력


마무리

이 매뉴얼에서는 Raspberry Pi 환경에서 GrowSpace 개발자 태그와 시리얼 통신을 구성하고, Python을 통해 lep, lec 명령어 기반의 위치 데이터를 실시간으로 수신 및 파싱하는 과정을 소개했습니다.

이 과정을 통해:

  • Raspberry Pi의 /dev/serial0 포트를 활용한 안정적인 통신 구성

  • Python 기반 파싱 로직으로 실시간 위치 정보 확인 가능

  • RTLS 실험 및 프로토타이핑 테스트베드로 확장 가능

실습 중 문제가 발생할 경우, TX/RX 교차 연결 여부와 포트 활성화 상태를 꼭 다시 확인해 주세요.

Last updated