# ESP32 연동

### **이 글에서는**&#x20;

GrowSpace 개발자 태그를 Arduino ESP32 보드에 연결하여, `lec`, `lep` 명령어를 통해 실시간 위치 데이터를 수신하고 파싱하는 방법을 안내합니다. 처음 ESP32와 태그를 연결해보는 분들도 쉽게 따라할 수 있도록 단계별로 친절하게 설명드리겠습니다.

ESP32는 UNO와 달리 다중 하드웨어 시리얼을 지원하며, 소형 테스트 환경 구성에 적합한 보드입니다

***

### 준비물

<table><thead><tr><th width="302">항목</th><th>용도</th></tr></thead><tbody><tr><td><p>ESP32 보드 (예: DevKitC)</p><p></p><p><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2Fr1b6Y9u09CoFyDoHxyMa%2Fimage.png?alt=media&#x26;token=7bec1ea0-9fb9-4478-929b-dc92b28ae743" alt=""></p></td><td>시리얼 수신 및 파싱</td></tr><tr><td><p>GrowSpace 개발자 태그</p><p></p><p><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FVRkgs2MkkQqDw9zIFBif%2Fimage.png?alt=media&#x26;token=39370a63-5c91-4531-a764-d74cb3046768" alt=""></p></td><td>위치 정보 송신 장치</td></tr><tr><td>Arduino IDE</td><td>코드 작성 및 업로드</td></tr><tr><td>점퍼 케이블 (4핀)</td><td>TX, RX, GND, 3.3V 연결용</td></tr></tbody></table>

***

### Arduino IDE 설정

#### 설치

* Arduino IDE 공식 페이지에서 설치 파일 다운로드\
  <https://www.arduino.cc/en/software&#x20>;
* Windows 사용자는 `.msi` 인스톨러 권장

#### ESP32 보드 추가

* Arduino IDE 실행 → `File > Preferences`

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2Fxl0qYQ6ITLrj5hIMfjg2%2Fimage.png?alt=media&#x26;token=46128810-e359-40cf-b4ec-e03ac3969766" alt=""><figcaption></figcaption></figure>

* **Additional Board Manager URLs**에 아래 주소 입력:

```
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
```

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2F1Ac2xzc54fSPtvZSrki3%2Fimage.png?alt=media&#x26;token=11ebb527-2b53-43c5-b92d-8d4c9160bb16" alt=""><figcaption></figcaption></figure>

* `Tools > Board > Board Manager` → ESP32 설치

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FCMeLzTdYkJRJTjdjf8PM%2Fimage.png?alt=media&#x26;token=a4eb1a29-0cf1-4036-bcc0-f6dfbd2437c0" alt=""><figcaption></figcaption></figure>

* 설치 후 `Tools > Board > ESP32 Dev Module` 선택
* `Tools > Port`에서 자동 인식된 포트 선택

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FXHcB2kDLP3MoReldiRzF%2Fimage.png?alt=media&#x26;token=fb25fef7-79a0-4d61-9caa-e5a6e4f8bfce" alt=""><figcaption></figcaption></figure>

***

### 시리얼 연결 구성

GrowSpace 개발자 태그의 **좌측 커넥터**는 3.3V 시리얼을 지원합니다.\
ESP32 보드의 TX2/RX2 핀에 아래와 같이 연결하세요:

| 개발자 태그 | ESP32 보드      |
| ------ | ------------- |
| TX     | GPIO 16 (RX2) |
| RX     | GPIO 17 (TX2) |
| GND    | GND           |
| 3.3V   | 3.3V          |

> ⚠️ TX ↔ RX는 반드시 교차 연결해야 합니다.\
> 예: 태그 TX → ESP32 RX, 태그 RX → ESP32 TX

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FHIXXCGbV0IFO3ovg5SQf%2Fimage.png?alt=media&#x26;token=1f4d7f2c-9866-41ec-9868-cf0aab83e54a" alt=""><figcaption></figcaption></figure>

아래는 ESP32 DevKit의 핀맵입니다. TX2/RX2의 위치를 확인하고 정확히 연결해 주세요:

<figure><img src="https://grow-space.io/wp-content/uploads/2025/06/image-15.png" alt="" height="383" width="759"><figcaption></figcaption></figure>

***

### 시리얼 중계 예제 코드

이 코드는 PC와 태그 간 시리얼 데이터를 ESP32가 중계하는 코드입니다.

```cpp
#define SERIAL2_RX 16  // GPIO16
#define SERIAL2_TX 17  // GPIO17

String inputFromSerial = "";
String inputFromSerial2 = "";

void setup() {
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, SERIAL2_RX, SERIAL2_TX);
  Serial.println("ESP32 Serial <-> Serial2 중계 (\\n 기준, \\r 추가, lep/lec 파싱)");
}

void loop() {
  // Serial 입력 → Serial2로 전송
  while (Serial.available()) {
    char ch = (char)Serial.read();
    if (ch != '\n') {
      inputFromSerial += ch;
    }

    if (ch == '\n') {
      Serial2.print(inputFromSerial);
      Serial2.print('\r');  // CR 추가
      inputFromSerial = "";
    }
  }

  // Serial2 입력 → 파싱 및 출력
  while (Serial2.available()) {
    char ch = (char)Serial2.read();
    inputFromSerial2 += ch;

    if (ch == '\n') {
      inputFromSerial2.trim();

      if (inputFromSerial2.startsWith("POS,")) {
        parseLEP(inputFromSerial2);
      } else if (inputFromSerial2.startsWith("DIST,")) {
        parseLEC(inputFromSerial2);
      } else {
        Serial.print("[Serial2 → Serial] 수신: ");
        Serial.println(inputFromSerial2);
      }

      inputFromSerial2 = "";
    }
  }
}
```

* 시리얼 모니터 설정: **115200bps**, `Newline` 전송 설정
* `si` 명령어를 입력했을 때 장비 정보가 출력되면 연결 성공입니다.

***

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

개발자 태그는 `lep` 또는 `lec` 명령어를 통해 현재 위치 정보를 반환합니다. 아래 코드는 해당 응답을 분석하고 보기 좋게 출력하는 예제입니다.

```cpp
#define SERIAL2_RX 16  // GPIO16
#define SERIAL2_TX 17  // GPIO17

String inputFromSerial = "";
String inputFromSerial2 = "";

void setup() {
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, SERIAL2_RX, SERIAL2_TX);
  Serial.println("ESP32 Serial <-> Serial2 중계 (\\n 기준, \\r 추가, lep/lec 파싱)");
}

void loop() {
  // Serial 입력 → Serial2로 전송
  while (Serial.available()) {
    char ch = (char)Serial.read();
    if (ch != '\n') {
      inputFromSerial += ch;
    }

    if (ch == '\n') {
      Serial2.print(inputFromSerial);
      Serial2.print('\r');  // CR 추가
      inputFromSerial = "";
    }
  }

  // Serial2 입력 → 파싱 및 출력
  while (Serial2.available()) {
    char ch = (char)Serial2.read();
    inputFromSerial2 += ch;

    if (ch == '\n') {
      inputFromSerial2.trim();

      if (inputFromSerial2.startsWith("POS,")) {
        parseLEP(inputFromSerial2);
      } else if (inputFromSerial2.startsWith("DIST,")) {
        parseLEC(inputFromSerial2);
      } else {
        Serial.print("[Serial2 → Serial] 수신: ");
        Serial.println(inputFromSerial2);
      }

      inputFromSerial2 = "";
    }
  }
}

// ✅ LEP 결과 파서: POS,x,y,z,qf
void parseLEP(String line) {
  Serial.println("[LEP 위치 결과]");
  int idx = 0;
  String parts[5];

  while (line.length() > 0 && idx < 5) {
    int comma = line.indexOf(',');
    if (comma == -1) {
      parts[idx++] = line;
      break;
    } else {
      parts[idx++] = line.substring(0, comma);
      line = line.substring(comma + 1);
    }
  }

  Serial.print("X: "); Serial.println(parts[1]);
  Serial.print("Y: "); Serial.println(parts[2]);
  Serial.print("Z: "); Serial.println(parts[3]);
  Serial.print("품질(QF): "); Serial.println(parts[4]);
  Serial.println();
}

// 안전한 split 유틸(간단)
static void splitByComma(const String& s, std::vector<String>& out) {
  out.clear();
  int start = 0;
  while (start <= s.length()) {
    int comma = s.indexOf(',', start);
    if (comma == -1) {
      out.push_back(s.substring(start));
      break;
    } else {
      out.push_back(s.substring(start, comma));
      start = comma + 1;
    }
  }
}

// ✅ LEC 결과 파서(안전한 토큰 파싱): DIST, n, (ANk, id, x, y, z, d)*, POS, x, y, z, qf
void parseLEC(String line) {
  Serial.println("[LEC 거리 + 위치 결과]");

  line.trim();
  std::vector<String> t;
  splitByComma(line, t);
  if (t.size() < 2 || t[0] != "DIST") {
    Serial.println("→ 형식 오류: DIST 헤더 없음");
    return;
  }

  int i = 1;
  int anchorCount = t[i++].toInt(); // 예: 4
  int parsedAnchors = 0;

  // Anchor 묶음 파싱: ANk, id, x, y, z, d
  while (i + 5 < (int)t.size() && t[i].startsWith("AN")) {
    String anLabel = t[i++];      // AN0, AN1, ...
    String anId    = t[i++];      // 3364, 35BE, ...
    float x = t[i++].toFloat();   // -1.00
    float y = t[i++].toFloat();   // 11.48
    float z = t[i++].toFloat();   // 0.00
    float d = t[i++].toFloat();   // 2.80

    Serial.print(anLabel); Serial.print(" (ID ");
    Serial.print(anId); Serial.print("): ");
    Serial.print("x="); Serial.print(x);
    Serial.print(", y="); Serial.print(y);
    Serial.print(", z="); Serial.print(z);
    Serial.print(" → 거리: "); Serial.print(d); Serial.println("m");

    parsedAnchors++;
  }

  if (parsedAnchors != anchorCount) {
    Serial.print("→ 경고: DIST가 가리킨 Anchor 수(");
    Serial.print(anchorCount);
    Serial.print(")와 실제 파싱 수(");
    Serial.print(parsedAnchors);
    Serial.println(") 불일치");
  }

  // POS, x, y, z, qf
  if (i < (int)t.size() && t[i] == "POS") {
    // parseLEP는 "POS,..." 전체 문자열을 기대하므로 그대로 만들어 전달
    String posLine = "POS";
    for (int k = i + 1; k < (int)t.size(); ++k) {
      posLine += ",";
      posLine += t[k];
    }
    Serial.println("▶ 태그 위치:");
    parseLEP(posLine);
  } else {
    Serial.println("→ POS 정보 없음");
  }
}
```

* lep 실행 결과

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FtMVTiaIz5qsJ522crBrW%2FLEP.png?alt=media&#x26;token=7c6795bd-13e0-4d8b-9257-937bac556326" alt=""><figcaption></figcaption></figure>

* lec 실행 결과

<figure><img src="https://2885569511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYKyrLLe7cm7ZOhiSB0gg%2Fuploads%2FXHquUatDUUZOZLgzOSWU%2FLEC.png?alt=media&#x26;token=8a01df82-48f8-404c-9a38-d5791a21353c" alt=""><figcaption></figcaption></figure>

***

### 테스트 방법 요약

* 위 코드를 업로드한 후 Arduino IDE의 시리얼 모니터 실행
* `lep` 또는 `lec` 명령어를 입력
* 위치 정보가 정상적으로 출력되는지 확인 (XYZ 좌표, 거리 등)

***

### 마무리

이 매뉴얼에서는 GrowSpace 개발자 태그를 ESP32와 연결하여, 시리얼 통신을 통해 위치 명령어를 입력하고 데이터를 수신/파싱하는 전체 흐름을 안내드렸습니다.

이 과정을 통해 단일 태그의 실시간 위치 추적이 가능하며, 이후 BLE 전송, Wi-Fi 연동, MQTT 연동 등 다양한 확장 실험으로 이어질 수 있습니다.

혹시 실습 중 연결이 안 되거나 응답이 없을 경우, TX/RX 핀 교차 연결 여부와 시리얼 포트 설정을 꼭 다시 확인해주세요!
