IT/IT기기

ESP32-S3로 Wake-on-LAN 안 될 때 Wi-Fi 원격 PC 켜기 (Geekble Nano USB HID WOL)

hexcode 2026. 7. 4. 23:39
반응형

ESP32-S3 프로젝트

Wi-Fi로 잠든 PC를 깨워라
ESP32-S3 USB HID WOL 만들기

Wake-on-LAN이 안 되는 Windows PC를, ESP32-S3를 USB 키보드처럼 동작시켜 Wi-Fi로 원격 절전 해제하는 장치를 직접 만든다.

ESP32-S3 Geekble Nano USB HID Wi-Fi Arduino IDE Wake-on-LAN

 

진행 현황
보드 구매
개봉
Arduino IDE
Board Package
Blink
USB HID
Wi-Fi
HTTP Server
Wake API
절전 해제 ✓

 

Introduction

프로젝트 소개 & 동작 원리

기존 Wake-on-LAN의 한계

Wake-on-LAN(WOL)은 네트워크로 PC를 원격 부팅하는 기술이다. 그런데 WOL에는 중요한 전제 조건이 있다.

WOL의 조건: Magic Packet을 보내는 장치와 대상 PC가 같은 유선 네트워크에 있어야 한다. 공유기, 서버, NAS 등 유선으로 연결된 디바이스가 반드시 필요하다. 무선(Wi-Fi)만으로는 WOL 신호를 안정적으로 전달하기 어렵다.

또한 USB 유선랜 어댑터는 제품 스펙에 WOL 지원이라고 표기되어 있어도 실제로는 동작하지 않는 경우가 많다. 특히 Mac 하드웨어에 Windows를 Boot Camp나 가상화로 설치한 환경에서는 내장 또는 USB 랜카드의 WOL이 거의 작동하지 않는다. 드라이버와 펌웨어 수준에서 지원이 끊기기 때문이다.

이 프로젝트의 접근법

이 프로젝트는 WOL을 완전히 다른 방식으로 우회한다. Logitech USB Receiver 같은 USB 입력 장치의 키 입력은 절전 상태에서도 PC를 깨울 수 있다는 점에 착안했다. ESP32-S3를 USB Keyboard처럼 동작시키고, Wi-Fi로 HTTP 요청을 받으면 Shift 키를 전송해 PC의 절전을 해제한다.

이 방법의 장점: 유선 네트워크나 별도 서버 없이 Wi-Fi만 있으면 된다. 공유기에 연결된 스마트폰이나 원격 PC에서 브라우저로 URL 하나만 호출하면 끝이다.

부품 비용

핵심 부품인 Geekble Nano ESP32-S3 하나로 모든 것이 해결된다. 쿠팡 기준 약 9,800원, 알리익스프레스 기준 5,000~6,000원 수준이다. WOL 지원 USB 랜카드를 사도 동작하지 않는 상황이라면, 이 방법이 훨씬 확실하다.

📱 스마트폰 / 원격 PC
브라우저에서 http://esp32.local/wake 호출
Wi-Fi (로컬 네트워크)
ESP32-S3 (Geekble Nano)
HTTP 요청 수신 → USB HID Shift 키 전송
USB HID Keyboard (가상)
Windows PC 절전 해제 ✓
이후 RDP 등으로 원격 접속
핵심 포인트: ESP32-S3는 USB OTG를 내장하고 있어 별도의 USB 컨트롤러 칩 없이도 USB HID 장치(키보드·마우스)를 소프트웨어만으로 구현할 수 있다.
Chapter 1

Geekble Nano ESP32-S3 소개

이번 프로젝트의 주인공은 Geekble Nano ESP32-S3이다. Arduino Nano와 거의 동일한 폼팩터에 ESP32-S3를 탑재한 보드로, USB HID를 하드웨어 레벨에서 지원한다는 점이 이 프로젝트에 딱 맞다.

주요 사양

항목 내용
SoC ESP32-S3FH4R2
CPU Dual Core Xtensa LX7, 240 MHz
Flash 4 MB
PSRAM 2 MB
연결 Wi-Fi 802.11 b/g/n, Bluetooth 5.0 (BLE)
USB USB OTG (USB-C) — HID / CDC 지원
크기 Arduino Nano 호환 폼팩터

왜 ESP32-S3인가?

USB HID 내장
USB CDC 내장
Wi-Fi 내장
Bluetooth 5.0
Dual Core 240MHz
Arduino 지원
Arduino Nano 크기

USB HID를 내장한 덕분에 별도의 USB 컨트롤러(예: ATmega16U2) 없이도 키보드·마우스로 동작할 수 있다. Wi-Fi까지 내장되어 있으니 이 프로젝트에 필요한 모든 조건을 혼자 갖추고 있다.

Chapter 2

보드 개봉

구성품은 심플하다. 박스를 열면 Geekble Nano 본체 하나만 들어있다.

ESP32 포장 상태
▲ 포장 상태
ESP32 보드 앞면
▲ 보드 앞면
ESP32 보드 뒷면
▲ 보드 뒷면 — GPIO 핀맵이 실크로 인쇄되어 있어 편리하다

앞면

앞면에는 다음 요소가 배치되어 있다.

USB-C 포트
BOOT 버튼
RESET 버튼
ESP32-S3FH4R2

뒷면

뒷면에는 GPIO 핀맵이 실크인쇄되어 있어 핀번호를 외우거나 데이터시트를 따로 찾지 않아도 된다. Arduino Nano 사용 경험이 있다면 동일한 크기로 그대로 활용할 수 있다.

팁: 보드 구입 전에 ESP32-S3 시리즈인지 꼭 확인하자. 일반 ESP32나 ESP32-S2는 USB HID 지원 여부가 다를 수 있다. 이 프로젝트는 반드시 ESP32-S3가 필요하다.
Chapter 3

Arduino IDE 설치

ESP32-S3는 Arduino IDE 2.x 에서 공식적으로 지원한다. 공식 홈페이지에서 최신 버전을 다운로드하자.

https://www.arduino.cc/en/software

Windows라면 Windows Installer를 다운로드해 일반적인 프로그램처럼 설치하면 된다.

드라이버 설치 — 중요!

Arduino IDE를 처음 실행하면 Windows 드라이버 설치 창이 나타난다. 이 프로젝트에서는 Adafruit Industries LLC 드라이버 설치 창이 표시되었다.

반드시 설치해야 한다. 건너뛰면 이후 ESP32 보드가 포트로 인식되지 않을 수 있다.
Arduino IDE 최초 실행 및 드라이버 설치 화면
▲ Arduino IDE 최초 실행 시 나타나는 드라이버 설치 화면
Additional Boards Manager URLs 설정
▲ Additional Boards Manager URLs 설정 화면
Chapter 4

ESP32 Board Package 설치

Arduino IDE는 기본적으로 AVR(아두이노 우노 등) 보드만 지원한다. ESP32-S3를 사용하려면 Espressif의 Board Package를 별도로 추가해야 한다.

Step 1 — Boards Manager URL 추가

File → Preferences (또는 Ctrl+,)를 열고
Additional Boards Manager URLs 항목에 아래 URL을 추가한다.

https://espressif.github.io/arduino-esp32/package_esp32_index.json

이미 다른 URL이 있다면 줄바꿈으로 구분해 추가하면 된다.

ESP32 보드 패키지 설치
▲ Boards Manager에서 "esp32" 검색 후 esp32 by Espressif Systems 설치

Step 2 — Board Package 설치

왼쪽 사이드바의 Boards Manager 아이콘(칩 모양)을 클릭하고 검색창에 esp32를 입력한다.
esp32 by Espressif Systems를 찾아 Install을 클릭한다. 설치에 수 분이 걸릴 수 있다.

버전 선택 팁: 특별한 이유가 없다면 최신 안정 버전을 설치하자. 설치 후 IDE를 재시작할 필요는 없다.

Step 3 — 보드 선택

Tools → Board → ESP32 Arduino → ESP32S3 Dev Module을 선택한다.
Geekble Nano는 전용 보드 정의가 없지만, ESP32S3 Dev Module 설정으로 가장 안정적으로 동작한다.

보드 선택
▲ Tools → Board → ESP32S3 Dev Module 선택

Step 4 — COM 포트 선택

Tools → Port에서 새로 생긴 COM 포트를 선택한다 (예: COM10).

COM 포트 선택
▲ Tools → Port에서 ESP32의 COM 포트 선택
포트가 보이지 않는다면? USB 케이블을 다시 꽂고 IDE 창을 한 번 클릭해 새로고침해 보자. 데이터 전송이 가능한 USB 케이블인지도 확인한다 (충전 전용 케이블은 불가).
Chapter 5

Blink 테스트 — 첫 번째 업로드

보드 설정이 끝났다면 가장 먼저 해야 할 것은 Blink 업로드다. LED 하나를 깜빡이는 단순한 코드지만, 이 단계 성공이 하드웨어·드라이버·보드 설정·업로드 경로가 모두 정상임을 한 번에 증명한다.

Geekble Nano의 내장 LED 핀

Geekble Nano는 보드 뒷면 실크인쇄에 D13 = GPIO48이 내장 LED라고 표시되어 있다. 기본 Blink 예제(LED_BUILTIN)가 올바른 핀을 가리키지 않을 수 있으므로, 아래 코드로 명시적으로 지정하는 것이 확실하다.

#define LED_PIN 48

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(500);
  digitalWrite(LED_PIN, LOW);
  delay(500);
}

업로드

Blink 업로드 중
▲ 업로드 진행 중 — 하단 로그에 진행률이 표시된다
Blink 업로드 완료
▲ 업로드 완료 — Hard resetting via RTS pin... 메시지가 성공 신호
성공 메시지 예시:
Wrote 301120 bytes ... in 1.4 seconds
Hash of data verified.
Hard resetting via RTS pin...

업로드 실패 시 (ESP32-S3에서 종종 발생)

ESP32-S3는 업로드 시 부트 모드 진입이 필요한 경우가 있다. 순서대로 시도해 보자.

방법 1: Upload 버튼 클릭 → 하단에 Connecting... 표시 시 BOOT 버튼 누른 채 유지 → 업로드 시작되면 손을 뗌

방법 2: BOOT 누른 상태 → RST 한 번 클릭 → BOOT 유지 → Upload 버튼 클릭

결과 확인

LED 깜빡임 결과
▲ 내장 LED(GPIO48)가 0.5초 간격으로 깜빡이는 모습 🎉

LED가 0.5초 간격으로 깜빡이면 성공이다. 이로써 하드웨어, 드라이버, 보드 설정, 업로드 경로가 모두 정상임이 확인되었다. 다음 단계인 USB HID 구현으로 넘어갈 준비가 완료되었다.

Chapter 6

USB HID Keyboard 구현

이제 본격적으로 이 프로젝트의 핵심인 USB HID Keyboard를 구현한다. ESP32-S3가 PC에 USB 키보드로 인식되고, 코드로 키 입력을 보낼 수 있게 된다.

Step 1 — USB 설정 확인

먼저 Tools 메뉴에서 현재 USB 관련 설정을 확인한다.

USB 설정 확인
▲ Tools 메뉴에서 USB 관련 설정 항목 확인

확인이 필요한 핵심 항목은 다음과 같다.

항목 설정값
Board ESP32S3 Dev Module
USB CDC On Boot Enabled (변경 필요)
USB Mode Hardware CDC and JTAG
Flash Size 4MB

Step 2 — USB CDC On Boot → Enabled

Tools → USB CDC On Boot → Enabled로 변경한다. 이 설정이 활성화되어야 부팅 후 USB 장치가 자동으로 초기화된다.

USB CDC On Boot Enabled 설정
▲ USB CDC On Boot를 Enabled로 변경한 모습
Arduino ESP32 3.x에서의 USB HID
ESP32 Arduino Core 3.x부터는 USB.h + USBHIDKeyboard.h 내장 라이브러리로 간단하게 USB HID를 구현할 수 있다. 별도의 TinyUSB 설치 없이도 동작한다.

Step 3 — 코드 작성 및 업로드

새 스케치를 만들고 아래 코드를 붙여넣은 후 업로드한다.

#include "USB.h"
#include "USBHIDKeyboard.h"

USBHIDKeyboard Keyboard;

void setup() {
  USB.begin();
  Keyboard.begin();

  // USB 인식 대기
  delay(3000);

  Keyboard.println("Hello World!");
  Keyboard.println("ESP32-S3 USB HID Keyboard Test");
}

void loop() {
}
USB 키보드 스케치 업로드
▲ USB HID Keyboard 스케치 작성 및 업로드

Step 4 — USB 키보드 인식 확인

업로드 후 PC가 ESP32-S3를 USB HID Keyboard 장치로 인식하는 것을 확인한다. Windows 장치 관리자 또는 알림에서 확인할 수 있다.

USB 키보드로 인식된 모습
▲ PC가 ESP32-S3를 USB HID Keyboard 장치로 인식한 모습

Step 5 — 메모장 입력 테스트

메모장(Notepad)을 열고 커서를 놓은 뒤, ESP32의 RST 버튼을 한 번 누른다. 3초 후 텍스트가 자동으로 입력되면 성공이다.

메모장 자동 입력 완료
▲ 메모장에 "Hello World!" 와 "ESP32-S3 USB HID Keyboard Test"가 자동 입력된 모습 🎉
동작 원리 정리
USB.begin() → USB 스택 초기화
Keyboard.begin() → HID Keyboard 장치 등록
delay(3000) → PC가 장치를 인식할 때까지 대기
Keyboard.println() → 키 입력 전송 (엔터 포함)

이 단계 성공으로 ESP32-S3가 실제 USB 키보드처럼 동작함을 확인했다. 다음 단계는 여기에 Wi-Fi를 더해 원격에서 이 키 입력을 트리거하는 것이다.

Chapter 7

Wi-Fi 연결

USB HID 동작이 확인되었으니, 이제 Wi-Fi를 연결한다. 이 단계의 목표는 ESP32-S3가 공유기에 접속해 IP를 할당받는 것이다.

USB HID + Wi-Fi 동시 사용 시 초기화 순서 주의
Arduino ESP32 Core 3.x에서는 USB와 Wi-Fi를 함께 쓸 때 초기화 순서가 중요하다. 검증된 순서: Keyboard.begin()USB.begin()WiFi.begin()server.begin()

Wi-Fi 연결 코드

#include <WiFi.h>

const char* ssid = "공유기이름";
const char* password = "비밀번호";

void setup() {
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // 연결 성공 — IP 확인
  // Serial.println(WiFi.localIP());
}

void loop() { }

공유기에서 IP 확인

ESP32가 공유기에 접속하면 DHCP로 IP를 할당받는다. 공유기 관리 페이지에서 연결된 장치 목록을 확인하거나, 시리얼 모니터로 WiFi.localIP()를 출력해 확인한다.

공유기에서 IP 확인
▲ 공유기 관리 페이지에서 ESP32의 IP 주소 확인
시리얼 모니터 출력
▲ 시리얼 모니터에서 Wi-Fi 연결 상태 및 IP 주소 확인
포트 복구 팁 (ESP32-S3 흔한 현상)
USB HID 모드로 동작 중에는 COM 포트가 사라지는 경우가 있다. 당황하지 말고 BOOT 버튼을 누른 채 RST → BOOT 유지 → Upload 순서로 복구한다.
포트 복구 화면
▲ COM 포트 사라짐 현상 및 BOOT 버튼으로 복구하는 화면
Chapter 8

HTTP Web Server 구현

Wi-Fi 연결이 확인됐으면 HTTP Web Server를 올린다. 스마트폰이나 원격 PC의 브라우저에서 ESP32에 HTTP 요청을 보낼 수 있게 된다.

WebServer 코드

#include <WiFi.h>
#include <WebServer.h>

WebServer server(80);

void handleRoot() {
  server.send(200, "text/html",
    "<h1>ESP32-S3 WakeKey32</h1>"
    "<p><a href='/wake'>Wake PC</a></p>"
  );
}

void setup() {
  // ... WiFi 연결 ...

  server.on("/", handleRoot);
  server.begin();
}

void loop() {
  server.handleClient();
}

웹서버 동작 확인

브라우저에서 http://[ESP32의 IP주소]/를 열면 ESP32가 응답하는 페이지가 표시된다.

ESP32 웹서버 동작 확인
▲ 브라우저에서 ESP32 웹서버에 접속 성공한 모습
mDNS 설정 (선택)
#include <ESPmDNS.h>MDNS.begin("esp32")를 추가하면 IP 주소 대신 http://esp32.local/로 접속할 수 있다. 같은 Wi-Fi 네트워크 내에서만 동작한다.
Chapter 9

최종 완성 코드 & 안정화 작업

WiFi + USB HID 통합 코드는 처음부터 바로 안정적이지 않았다. 동작은 했지만 USB 인식이 끊기거나 키보드가 불안정한 증상이 계속 나타났다. 원인을 하나씩 추적해 잡아낸 과정을 그대로 기록한다.

발견된 버그와 불안정 원인

핵심 버그: setTxPower가 실제로는 한 번도 적용된 적 없었다

WiFi.setTxPower()WiFi.begin() 전에 호출하면 WiFi 드라이버가 아직 시작되지 않은 상태라 조용히 무시된다. 즉, 8.5dBm으로 출력을 제한한다고 생각했지만 실제로는 계속 최대 출력(~20dBm)으로 송신하고 있었던 것이다. WiFi 송신 시 300mA 이상의 전류 스파이크가 USB 열거 타이밍과 겹치면서 인식이 불안정했던 근본 원인이었다.

6가지 안정화 개선사항

항목 변경 내용 효과
① setTxPower 위치 WiFi.begin() 뒤로 이동 8.5dBm 제한이 이제 실제로 적용됨 — 가장 효과 큼
② USB 초기화 순서 USB 먼저 → tud_mounted() 대기 → WiFi USB 열거 완료 후에 WiFi 전류 스파이크가 오도록 분리
③ CPU 클럭 240MHz → 160MHz 성능은 충분하고 전류 스파이크 여유 확보
④ WiFi 모뎀 슬립 WiFi.setSleep(true) 전송 없을 때 WiFi 라디오 슬립 → 평균 전류 대폭 감소
⑤ loop() delay delay(2) 추가 CPU idle → 모뎀 슬립이 실제로 동작할 시간 확보
⑥ Remote Wakeup 선언 USB.usbAttributes(...REMOTE_WAKEUP) USB 디스크립터에 선언 → Windows 전원 관리 탭 생성

USB Remote Wakeup 디스크립터 설정

Windows 장치 관리자에 전원 관리 탭이 생기려면 USB 디스크립터에 Remote Wakeup 지원이 선언되어 있어야 한다. 이 한 줄을 USB.begin() 전에 호출해야 한다.

// 반드시 USB.begin() 전에 호출
USB.usbAttributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP);
tud_mounted() 대기의 의미
USB.begin()을 호출했다고 바로 PC에 인식되는 게 아니다. PC가 USB 장치를 열거(enumerate)해서 드라이버를 매칭할 때까지 기다려야 한다. tud_mounted()true를 반환하면 그때 USB가 완전히 인식된 것이다. 이 확인 없이 WiFi를 켜면 인식 도중 전류 스파이크가 겹친다.

최종 완성 코드

#include <WiFi.h>
#include <WebServer.h>
#include <esp_system.h>
#include "USB.h"
#include "USBHIDKeyboard.h"
#include "tusb.h"

const char* ssid = "공유기이름";
const char* password = "비밀번호";

WebServer server(80);
USBHIDKeyboard Keyboard;

const char* resetReasonStr() {
  switch (esp_reset_reason()) {
    case ESP_RST_POWERON: return "Power-on";
    case ESP_RST_SW: return "Software reset";
    case ESP_RST_PANIC: return "Panic (crash)";
    case ESP_RST_BROWNOUT: return "BROWNOUT (power drop!)";
    case ESP_RST_WDT:
    case ESP_RST_INT_WDT:
    case ESP_RST_TASK_WDT: return "Watchdog";
    default: return "Other";
  }
}

void handleRoot() {
  server.send(200, "text/html",
    "<h2>ESP32 WakeKey32</h2>"
    "<p><a href='/wake'>Wake (Shift)</a></p>"
    "<p><a href='/type?text=Hello'>Type Hello</a></p>"
    "<p><a href='/status'>Status</a></p>"
  );
}

void handleWake() {
  if (tud_suspended()) {
    tud_remote_wakeup(); // USB Remote Wakeup 신호 전송
    delay(1000); // 호스트가 버스를 재개할 때까지 대기
  }
  Keyboard.press(KEY_LEFT_SHIFT);
  delay(100);
  Keyboard.release(KEY_LEFT_SHIFT);
  server.send(200, "text/plain", "Wake signal sent!");
}

void handleType() {
  if (server.hasArg("text")) {
    Keyboard.print(server.arg("text"));
    server.send(200, "text/plain", "Typed: " + server.arg("text"));
  } else {
    server.send(400, "text/plain", "Missing ?text=");
  }
}

void handleStatus() {
  String msg = "WiFi : " + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\n";
  msg += "IP : " + WiFi.localIP().toString() + "\n";
  msg += "RSSI : " + String(WiFi.RSSI()) + " dBm\n";
  msg += "USB : " + String(tud_mounted() ? "Mounted" : "Not mounted") + "\n";
  msg += "Reset : " + String(resetReasonStr()) + "\n";
  msg += "Uptime: " + String(millis() / 1000) + " s";
  server.send(200, "text/plain", msg);
}

void setup() {
  setCpuFrequencyMhz(160); // 240→160MHz: 전류 스파이크 여유 확보

  // USB 디스크립터에 Remote Wakeup 선언 (반드시 USB.begin() 전)
  USB.usbAttributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP);
  Keyboard.begin();
  USB.begin();

  // USB 열거 완료 대기 (최대 3초)
  uint32_t t0 = millis();
  while (!tud_mounted() && millis() - t0 < 3000) delay(50);
  delay(500); // 열거 직후 안정화 여유

  WiFi.persistent(false);
  WiFi.setAutoReconnect(true);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  WiFi.setTxPower(WIFI_POWER_8_5dBm); // begin() 후에 호출해야 실제 적용
  WiFi.setSleep(true); // 모뎀 슬립: 평균 전류 감소

  t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) delay(250);

  server.on("/", handleRoot);
  server.on("/wake", handleWake);
  server.on("/type", handleType);
  server.on("/status", handleStatus);
  server.begin();
}

void loop() {
  server.handleClient();
  delay(2); // 모뎀 슬립 동작 시간 확보
}
최종 코드 업로드 성공 화면
▲ 최종 코드 업로드 성공 — Wrote 983040 bytes, Hard resetting via RTS pin...
최종 완성 인덱스 페이지
▲ 완성된 index 페이지 — Wake (Shift) / Type 'Hello' / Status 세 가지 엔드포인트로 접속 가능
type 엔드포인트로 Hello 입력 테스트
▲ /type?text=Hello 호출 결과 — 메모장에 "Hello"가 자동 입력된 모습

/status 진단 페이지

Serial 없이 동작 상태를 확인할 수 있는 진단 엔드포인트다. USB 인식 불안정 증상이 생기면 http://[IP]/status를 열어보자.

WiFi : Connected
IP : 192.168.0.xxx
RSSI : -52 dBm
USB : Mounted
Reset : Power-on
Uptime: 42 s
Reset: BROWNOUT (power drop!) 이 뜨면 코드가 아니라 전원 문제다.
PC 뒷면 USB 포트(메인보드 직결)로 바꾸거나 USB 3.0 포트(900mA)를 사용하자. RSSI가 -75dBm보다 나쁘면 WIFI_POWER_11dBm으로 한 단계 올리는 것을 고려한다.
Troubleshooting

트러블슈팅 — 실제로 겪은 문제들

이 프로젝트를 진행하면서 실제로 마주쳤던 문제들과 해결 방법을 정리했다. ESP32-S3를 처음 사용한다면 반드시 한 번씩 겪게 되는 내용들이다.

① 업로드 후 COM 포트가 사라짐

USB HID 모드로 전환되면 Windows가 ESP32를 시리얼 포트가 아닌 HID 키보드로 인식해 COM 포트가 사라진다.

해결: 아래 순서로 부트로더 모드 진입 후 업로드한다.

1. BOOT 버튼 누른 채 유지
2. RST 버튼 짧게 눌렀다 뗌
3. BOOT 버튼 뗌
4. Arduino IDE에서 Upload 클릭

Serial Monitor에 waiting for download가 보이면 부트로더 모드 진입 성공. 이 상태에서 Upload하면 COM 포트 없이도 업로드된다.
포트 복구 후 업로드
▲ 부트로더 모드로 진입 후 업로드 성공

② Hard resetting via RTS pin... 후 멈춤

업로드 마지막에 이 메시지가 나오면 업로드는 성공한 것이다. ESP32가 자동으로 리셋되어 새 코드를 실행한다. 아무것도 누를 필요 없다.

만약 이 메시지 이후 부트로더 대기 상태(waiting for download)로 빠지면 RST 버튼만 한 번 눌러 정상 부팅한다.

③ WiFi가 붙었다 끊어지기 반복

ESP32 WiFi 라디오가 켜질 때 순간 전류(최대 500mA)를 소비한다. PC USB 포트 전력이 불충분하면 전압 강하로 연결이 불안정해진다.

해결책 (우선순위 순):
1. 스마트폰 충전기(5V 1A 이상)로 전원 교체
2. USB 허브 대신 PC 본체 직접 연결
3. 코드에 WiFi.setTxPower(WIFI_POWER_8_5dBm) 추가로 송신 출력 감소
PC와 ESP32 보드 직결 - 짧은 케이블
▲ PC 본체 USB 포트에 짧은 케이블로 직결 — 전력 손실 최소화

④ Serial Monitor에 아무것도 안 나옴

Serial Monitor를 열기 전에 출력이 이미 지나간 경우다. Serial Monitor를 먼저 열고 RST 버튼을 눌러 재부팅하면 처음부터 출력된다. Baud rate가 코드와 동일한지(115200)도 확인한다.

⑤ USB HID와 Serial(CDC) 충돌

USB CDC On Boot: Enabled 상태에서 Serial.begin()USB.begin()을 같이 쓰면 포트가 사라지거나 충돌이 발생한다.

해결: USB CDC On Boot: Enabled일 때는 Serial이 이미 USB CDC 포트에 연결되어 있으므로 Serial.begin()을 따로 호출할 필요가 없다. Serial.begin()USB.begin()을 함께 호출하면 두 초기화가 같은 USB 포트를 두고 충돌해 COM 포트가 사라지거나 불안정해진다. Serial.print() 등은 별도 초기화 없이 그대로 USB CDC로 출력된다.

⑥ USB HID + WiFi 초기화 순서 문제

USB.begin()을 WiFi보다 먼저 실행하면 USB FreeRTOS 태스크가 WiFi 스택 초기화를 방해해 WiFi가 연결되지 않는다.

검증된 순서:
WiFi.begin() → 연결 대기 → server.begin()Keyboard.begin()USB.begin()
Chapter 10

Windows 절전 해제 — 실전 테스트

USB HID 키 입력만으로는 절전 중인 PC를 깨울 수 없다. PC가 절전 상태일 때 USB 장치가 PC를 깨우려면 USB 버스에 Remote Wakeup 신호를 별도로 전송해야 한다. 일반 무선 키보드 동글이 절전 해제가 되는 것도 이 신호를 보내기 때문이다.

Step 1 — 코드 수정: tud_remote_wakeup() 추가

Arduino ESP32 Core 3.x에서 USB.wakeupHost()는 존재하지 않는다. TinyUSB를 직접 사용해야 한다. #include "tusb.h"를 추가하고 tud_remote_wakeup()으로 신호를 보낸다.

#include <WiFi.h>
#include <WebServer.h>
#include "USB.h"
#include "USBHIDKeyboard.h"
#include "tusb.h" // tud_remote_wakeup() 사용을 위해 추가

const char* ssid = "공유기이름";
const char* password = "비밀번호";

WebServer server(80);
USBHIDKeyboard Keyboard;

void handleWake() {
  if (tud_suspended()) {
    tud_remote_wakeup(); // USB Remote Wakeup 신호 전송 (핵심)
  }
  delay(500); // PC가 깨어날 때까지 대기
  Keyboard.press(KEY_LEFT_SHIFT);
  delay(100);
  Keyboard.release(KEY_LEFT_SHIFT);
  server.send(200, "text/plain", "Wake signal sent!");
}

void handleStatus() {
  String msg = "WiFi : Connected
";
  msg += "IP : " + WiFi.localIP().toString() + "
";
  msg += "USB : OK
";
  msg += "Suspended: ";
  msg += tud_suspended() ? "Yes" : "No";
  server.send(200, "text/plain", msg);
}

void setup() {
  WiFi.persistent(false);
  WiFi.setAutoReconnect(true);
  WiFi.setTxPower(WIFI_POWER_8_5dBm);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  server.on("/wake", handleWake);
  server.on("/status", handleStatus);
  server.begin();

  Keyboard.begin();
  USB.begin();
}

void loop() {
  server.handleClient();
}
동작 확인 방법: 절전 후 http://[IP]/status에서 Suspended: Yes가 뜨면 USB가 정상적으로 suspend 상태. 이 상태에서 /wake 호출 시 Remote Wakeup 신호가 전송된다.

Step 2 — Windows 장치 관리자 설정

Windows는 기본적으로 모든 USB 장치의 절전 해제를 허용하지 않는다. ESP32 HID 키보드 장치에 대해 아래 설정을 PC가 켜진 상태에서 미리 해두어야 한다.

반드시 PC가 켜진 상태에서 설정. 절전 후에는 설정 불가.
1. 장치 관리자 열기 (Win+X → 장치 관리자)
2. 휴먼 인터페이스 장치(HID) 펼치기
3. ESP32 HID Keyboard 장치 → 우클릭 → 속성
4. 전원 관리 탭 → "이 장치를 사용하여 컴퓨터의 대기 모드를 종료할 수 있음" 체크
5. 확인
장치 관리자 전원 관리 설정
▲ HID Keyboard 속성 → 전원 관리 탭 → 절전 해제 허용 체크

Step 3 — 실전 테스트 ✅

Windows PC를 절전 모드로 진입시킨 뒤, 한참 후에 다른 PC에서 아래 방법 중 하나로 호출했다.

호출 방법 3가지 — 모두 동작 확인

① 브라우저 주소창:
http://192.168.0.12/wake

② curl (터미널/명령 프롬프트):
curl http://192.168.0.12/wake

③ 인덱스 페이지에서 클릭:
http://192.168.0.12/ → Wake (Shift) 링크 클릭
Wake signal sent 응답 화면
▲ /wake 호출 응답 — "Wake signal sent!" 가 뜨면 Remote Wakeup 신호와 Shift 키 입력이 정상 전송된 것

세 가지 방법 모두 절전 중인 Windows PC가 정상적으로 깨어나는 것을 확인했다. 이로써 이 프로젝트의 최종 목표가 달성되었다.

동작 흐름 최종 확인
다른 PC / 스마트폰 브라우저
http://[ESP32 IP]/wake HTTP 요청
→ ESP32가 tud_remote_wakeup()으로 USB Remote Wakeup 신호 전송
→ Windows가 절전에서 깨어남
→ 이후 RDP 등으로 원격 접속 가능 ✅

최종 코드 다운로드

이 프로젝트의 최종 완성 코드(.ino)를 그대로 내려받아 사용할 수 있다.

다운로드 전 필수 수정: 코드 안의 ssid, password 값은 YOUR_WIFI_SSID / YOUR_WIFI_PASSWORD 로 치환되어 있다. 업로드 전에 반드시 본인의 공유기 SSID와 비밀번호로 바꿔야 한다.

이어서 읽기 — 2탄

데스크탑 PC를 완전종료 상태에서도 깨우고 싶다면(BIOS 설정 포함), 알리익스프레스 ESP32-S3 보드로 진행한 2탄도 참고하자. 코드는 이 글과 100% 동일하다.

ESP32-S3 Geekble Nano · Wi-Fi USB HID WOL 프로젝트

실제 진행 과정을 그대로 기록한 프로젝트입니다. 시행착오 포함.

반응형