본문으로 건너뛰기
ESP32-C3 Mastering · 7/12

ESP32-C3 WiFi 4 스택 — Station·SoftAP·Mesh 구성

· Hawk · 8분 읽기

#한 줄 요약

“ESP32-C3의 WiFi는 event-driven입니다. esp_wifi_* 호출은 명령일 뿐, 결과는 모두 event handler로 돌아옵니다.” 동기 API로 착각하면 race condition과 reconnect 지옥이 시작됩니다.

ESP32-C3는 802.11 b/g/n을 지원합니다. 2.4 GHz 단일 밴드이고, 1×1 SISO 안테나, 최대 PHY 비율 72.2 Mbps입니다. 실제 애플리케이션 throughput은 TCP 약 20 Mbps, UDP 약 30 Mbps가 한계입니다. 5 GHz도 802.11ac/ax도 없습니다. 대신 대량 보급용 IoT에 충분한 사양을 작은 RAM 풋프린트로 제공합니다.

이번 장에서는 WiFi 스택의 4가지 모드를 차례로 살펴보고, event loop 패턴, WPA2/WPA3 보안, ESP-MESH의 자가 조직 트리, 그리고 provisioning(공장 출하 후 사용자 WiFi 정보를 받는 절차)까지 다룹니다.

#ESP-IDF WiFi 스택 구조

WiFi 스택은 4개 계층입니다.

계층역할주요 API
DriverRF/MAC HALesp_wifi_init, esp_wifi_start
LwIPTCP/IP 스택esp_netif_*
Event Loop비동기 이벤트 라우팅esp_event_loop_create_default
Application비즈니스 로직사용자 코드

핵심은 event loop입니다. WiFi 연결 시도, 연결 성공, DHCP 완료, 끊김 같은 모든 상태 변화는 event로 발행됩니다. 애플리케이션은 handler를 등록해 비동기로 반응합니다.

#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "nvs_flash.h"
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
// 재연결 로직. 백오프와 retry 카운트 필수.
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
ESP_LOGI("wifi", "got IP: " IPSTR, IP2STR(&event->ip_info.ip));
}
}

WIFI_EVENT링크 계층 이벤트(연결·끊김·스캔 완료), IP_EVENT네트워크 계층 이벤트(IP 획득·갱신·상실)입니다. 두 베이스를 분리해 등록해야 의미가 분명해집니다.

#Station 모드 — 클라이언트 연결

가장 흔한 모드입니다. 가정용 라우터에 클라이언트로 붙는 시나리오입니다.

void wifi_init_sta(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = "MyHomeWiFi",
.password = "supersecret",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = { .capable = true, .required = false },
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}

호출 순서가 엄격합니다. nvs_flash_initesp_netif_initesp_event_loop_create_defaultesp_netif_create_default_wifi_staesp_wifi_initesp_wifi_set_modeesp_wifi_set_configesp_wifi_start. 한 단계라도 빠지거나 순서가 바뀌면 ESP_ERR_WIFI_NOT_INIT 또는 ESP_ERR_INVALID_STATE가 반환됩니다.

esp_wifi_start연결을 시작하지 않습니다. 오직 드라이버를 켤 뿐입니다. 실제 연결은 WIFI_EVENT_STA_START event를 받은 뒤 esp_wifi_connect를 호출해야 시작됩니다.

#SoftAP 모드 — AP 동작

ESP32-C3가 AP 노릇을 합니다. 폰을 직접 붙여 설정 화면을 띄우는 OOBE(out-of-box experience) 시나리오, 또는 별도 라우터 없이 센서 노드를 모으는 용도입니다.

void wifi_init_softap(void)
{
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.ap = {
.ssid = "ESP32-C3-Setup",
.ssid_len = strlen("ESP32-C3-Setup"),
.password = "configme",
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
.channel = 6,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}

기본 제약은 동시 클라이언트 10대 이하, DHCP server 자동 활성화, IP 대역 192.168.4.1/24입니다. 채널은 1·6·11이 안전합니다. 한 SoftAP가 너무 많은 클라이언트를 받으면 처리량이 급격히 떨어집니다. C3는 단일 코어에 RAM도 작아 4~6대가 현실적 한계입니다.

#Station+AP 동시 모드 — 브리지

가장 유용한 시나리오 중 하나입니다. 기존 라우터에 붙으면서 동시에 자체 AP도 운영합니다. provisioning, mesh root, range extender에 쓰입니다.

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));

주의할 점은 두 인터페이스가 같은 채널을 써야 한다는 것입니다. ESP32-C3 라디오는 하나뿐이라, STA가 6번 채널에 연결되면 AP도 자동으로 6번으로 끌려갑니다. AP 채널을 따로 지정해도 무시됩니다.

#WPA2-Personal vs WPA3-Personal

C3는 WPA3-PersonalWPA2/WPA3 Mixed까지 지원합니다.

보안키 교환C3 지원비고
Open없음yes보안 없음
WEPRC4yes (호환용)사용 금지
WPA2-Personal4-way handshakeyes사실상 표준
WPA3-PersonalSAE (Dragonfly)yesKRACK 면역
WPA2/WPA3 Mixed양쪽yes권장
WPA2-EnterpriseEAP-TLS, PEAPyes인증서 필요

WPA3로 갈 때는 *PMF(Protected Management Frames)*가 필수입니다. pmf_cfg.required = true로 켜야 합니다. WPA2/WPA3 mixed에서는 capable = true, required = false로 두는 것이 호환성 최대입니다.

wifi_config_t wifi_config = {
.sta = {
.ssid = "MyWiFi",
.password = "secret",
.threshold.authmode = WIFI_AUTH_WPA2_WPA3_PSK,
.pmf_cfg = { .capable = true, .required = false },
.sae_pwe_h2e = WPA3_SAE_PWE_HASH_TO_ELEMENT,
},
};

WPA2-Enterprise는 esp_wifi_sta_wpa2_ent_* 별도 API를 호출합니다. 사내 인증서(CA, client cert, private key)를 PEM으로 임베드하거나 NVS에 저장합니다.

#ESP-MESH — 자가 조직 트리 네트워크

ESP-MESH는 *root → layer 1 → layer 2 → …*의 트리입니다. 최대 6 layer까지 확장됩니다. mesh 안에서는 각 노드가 동시에 station(부모에게)·SoftAP(자식에게) 두 역할을 합니다.

ESP-MESH — Tree Topology

root만 외부 라우터에 붙습니다. 그 아래는 내부 mesh 트래픽만 흐릅니다. 노드가 떨어져 나가면 자동으로 부모를 재선출합니다. 한국에서는 공장 IoT 센서 네트워크대규모 LED 조명 제어에 쓰입니다.

mesh_cfg_t mesh_cfg = MESH_INIT_CONFIG_DEFAULT();
memcpy(mesh_cfg.mesh_id.addr, MESH_ID, 6);
mesh_cfg.channel = 0; // 0이면 root가 채널을 결정
mesh_cfg.router.ssid_len = strlen(ROUTER_SSID);
memcpy(mesh_cfg.router.ssid, ROUTER_SSID, mesh_cfg.router.ssid_len);
memcpy(mesh_cfg.router.password, ROUTER_PASS, strlen(ROUTER_PASS));
mesh_cfg.mesh_ap.max_connection = 6;
memcpy(mesh_cfg.mesh_ap.password, MESH_AP_PASS, strlen(MESH_AP_PASS));
ESP_ERROR_CHECK(esp_mesh_set_config(&mesh_cfg));
ESP_ERROR_CHECK(esp_mesh_start());

mesh는 대역폭이 layer마다 절반으로 떨어집니다. layer 3쯤 가면 throughput이 1 Mbps 이하로 내려갑니다. 고대역 application에는 부적합하고, 주기적 small payload에 적합합니다.

#Provisioning — 사용자 WiFi 정보 받기

공장 출하 단계에서는 어느 라우터에 붙을지 알 수 없습니다. 사용자가 처음 켤 때 알려 줘야 합니다. ESP-IDF는 wifi_provisioning 컴포넌트를 제공합니다.

방식채널사용자 경험
SoftAP provisioningESP가 AP, 폰이 STA폰을 ESP의 AP에 붙임
BLE provisioningBLE GATT폰 앱이 BLE로 전달
SmartConfigbroadcast 패킷폰이 WiFi packet에 정보 embed
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble,
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,
};
ESP_ERROR_CHECK(wifi_prov_mgr_init(config));
const char *pop = "abcd1234"; // proof of possession
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(
WIFI_PROV_SECURITY_1, pop, "PROV_C3", NULL));

WIFI_PROV_SECURITY_1은 X25519 키 교환 + AES-CTR, _SECURITY_2는 SRP6a + AES-GCM입니다. 양산 제품에서는 Security 2 + 디바이스별 username/password를 권장합니다. PoP(proof of possession)는 제품 라벨에 인쇄된 코드가 흔한 패턴입니다.

#전력 절약 — Modem Sleep와 DTIM

연결 상태에서 지속 전류를 줄이는 핵심은 modem sleep입니다. STA가 AP에 DTIM beacon만 듣는다는 약속을 보내고, 그 사이는 라디오를 끕니다.

ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM));
// WIFI_PS_MIN_MODEM — DTIM마다 깸 (기본)
// WIFI_PS_MAX_MODEM — 사용자 지정 listen_interval마다 깸
// WIFI_PS_NONE — 절전 끔

DTIM은 AP가 broadcast 전에 알려 주는 beacon 주기입니다. 가정용 라우터는 보통 DTIM=1 또는 3입니다. listen_interval을 DTIM의 배수로 키우면 깨는 빈도가 N배 감소하지만, broadcast packet을 놓칠 위험도 늘어납니다.

listen_interval평균 전류응답 지연
115 mA< 100 ms
35 mA< 300 ms
101.5 mA< 1 s
30< 500 µA< 3 s

#처리량의 현실

C3 데이터시트가 약속하는 PHY 비율은 72.2 Mbps입니다. 애플리케이션에서 실제로 나오는 throughput은 훨씬 낮습니다.

시나리오TCPUDP
STA → AP, 1 m 거리18~20 Mbps28~30 Mbps
STA → AP, 5 m 벽 1개12~15 Mbps20~25 Mbps
STA → AP, 10 m 벽 2개5~8 Mbps10~15 Mbps
Mesh L2 노드3~5 Mbps5~8 Mbps

이유는 여러 가지입니다. C3는 1×1 SISO라 MIMO 이득이 없고, 2.4 GHz는 간섭이 심하며, IDF의 LwIP 구현이 zero-copy가 아니라 사본을 한 번 더 만들기 때문입니다. 벌크 전송이 주된 application이면 ESP32-S3(2×2 듀얼 코어)나 ESP32-C6(WiFi 6) 쪽이 적합합니다.

#자주 하는 실수와 troubleshooting

증상원인해결
WIFI_EVENT_STA_START 뒤 무반응handler에서 esp_wifi_connect 안 함handler 안에서 connect 호출
endless reconnect 루프disconnect handler가 즉시 retry백오프(exponential) + 카운트 제한
ESP_ERR_WIFI_NOT_INITesp_netif_init 누락순서 엄수
AP 채널 무시됨STA가 다른 채널에 붙음APSTA에서는 STA 채널 따라감
WPA3 연결 실패PMF 옵션 미설정pmf_cfg.capable = true
throughput이 1 Mbps 이하DTIM listen_interval 너무 큼WIFI_PS_NONE으로 테스트
mesh root election 실패mesh_id 불일치 또는 channel 0 미통일모든 노드 같은 mesh_id, channel 0 권장

가장 흔한 함정은 reconnect 폭주입니다. WIFI_EVENT_STA_DISCONNECTED 핸들러에서 바로 esp_wifi_connect를 호출하면, AP가 다운된 상태에서 초당 수십 회 재시도가 발생합니다. 백오프(1s → 2s → 4s → 8s)와 최대 retry count를 반드시 둡니다.

#정리

  • ESP32-C3의 WiFi는 event-driven입니다. esp_wifi_* 호출은 명령일 뿐 결과는 event handler로 옵니다.
  • 모드는 4가지입니다. STA, SoftAP, APSTA, NULL. APSTA는 라디오 하나라서 채널이 통일됩니다.
  • 보안은 WPA2-Personal이 기본, WPA2/WPA3 Mixed가 권장입니다. WPA3는 PMF가 필수입니다.
  • ESP-MESH는 최대 6 layer의 트리이고 root만 외부 라우터에 붙습니다. 대역폭은 layer마다 절반으로 줍니다.
  • Provisioning은 BLE 방식이 양산에 가장 깔끔합니다. PoP는 제품 라벨 코드가 표준입니다.
  • Modem sleep + DTIM listen_interval로 STA 평균 전류를 15 mA → 1.5 mA까지 끌어내릴 수 있습니다.
  • 실제 throughput은 TCP 약 20 Mbps, UDP 약 30 Mbps가 최선입니다. C3는 IoT용이지 벌크 전송용이 아닙니다.
  • reconnect 폭주는 신규 펌웨어의 1순위 버그입니다. 백오프와 retry count는 처음부터 박아 두는 것이 안전합니다.

#다음 편

Ch 8: BLE 5.0 — GAP·GATT·Coded PHY에서는 또 다른 무선 인터페이스인 Bluetooth Low Energy 5.0을 다룹니다. NimBLE 스택, GATT 서비스 설계, Coded PHY로 4배 거리 확장하기까지 살펴봅니다.

#관련 항목