ESP32 Wi-Fi 联网第一课:STA/AP 双模式、连接管理与 HTTP 请求实战

里程碑:到这里,你已经会用 GPIO、ADC、PWM、总线与本地外设对话。接通 Wi-Fi 之后,ESP32 从「单片机」升级为「物联网设备」——数据能上云、手机能控制。


一、三种工作模式

模式角色场景
STA(Station)连接路由器的「客户端」设备联网拿数据、上报传感器
AP(Access Point)自己发出热点无路由器时的配置入口(手机直连设备)
AP+STA同时两者配网模式经典架构:AP 收配置 → STA 连路由
WiFi.mode(WIFI_STA);              // 客户端模式(最常用)
WiFi.mode(WIFI_AP);               // 热点模式
WiFi.mode(WIFI_AP_STA);           // 双模式

经典 ESP32 仅支持 2.4GHz——公司/家里的 5GHz 热点搜不到属正常,不是故障。


二、连接:阻塞版与事件版

1. 阻塞版(原型验证够用)

#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("你的SSID", "你的密码");

  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.printf("\n已连接! IP: %s\n", WiFi.localIP().toString().c_str());
}

问题:连不上就死等(还记得定时器篇的看门狗吗?)。

2. 事件驱动版(生产姿势)

WiFiEventHandler onGotIP, onDisconnect;

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);            // 断线自动重连
  WiFi.persistent(false);                 // 不把密码写 Flash(省 NVS 磨损)

  onGotIP = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &e) {
    Serial.printf("已连接, IP: %s\n", e.ip.toString().c_str());
  });
  onDisconnect = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected &e) {
    Serial.println("断线,自动重连中...");
  });

  WiFi.begin("你的SSID", "你的密码");
}

要点:别在 loop 里反复 WiFi.begin()——每次调用都重启射频,反而更连不上。begin 一次 + 自动重连 + 事件回调,是标准架构。


三、HTTP 请求:GET 与 POST

#include <WiFi.h>
#include <HTTPClient.h>

void fetchWeather() {
  HTTPClient http;
  http.begin("http://api.example.com/weather?city=shenzhen");
  int code = http.GET();                 // 发起请求

  if (code == 200) {
    String payload = http.getString();  // 响应体
    Serial.println(payload);
  }
  http.end();                            // 释放连接(必写!)
}

void postData() {
  HTTPClient http;
  http.begin("http://api.example.com/log");
  http.addHeader("Content-Type", "application/json");
  int code = http.POST("{\"temp\":25.5}");
  Serial.printf("POST 状态: %d\n", code);
  http.end();
}

HTTPS:URL 换 https:// 即可(esp32 内置根证书校验,个别自签服务需配置)。生产代码建议一律 HTTPS。


四、ArduinoJson:解析响应

#include <ArduinoJson.h>

void parseWeather(String json) {
  JsonDocument doc;                          // v7 自动容量
  DeserializationError err = deserializeJson(doc, json);
  if (err) { Serial.println("JSON 解析失败"); return; }

  const char* city  = doc["city"];
  float temp        = doc["temperature"];
  int humidity      = doc["humidity"];

  Serial.printf("%s: %.1f°C, 湿度 %d%%\n", city, temp, humidity);
}

v6 与 v7 API 不同(StaticJsonDocument/DynamicJsonDocument vs JsonDocument),报「未定义」时先查库版本——与 LEDC 的新旧 API 分水岭同款剧情。


五、实战:温湿度上报完整闭环

把本专栏已学全部串起来(L2 验收项目之一):

// 架构:定时器(采集) → I2C(读AHT20) → Wi-Fi(HTTP上报) → 非阻塞主循环
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

uint32_t tReport = 0;

void loop() {
  if (WiFi.status() == WL_CONNECTED && millis() - tReport > 60000) {
    tReport = millis();
    float temp = readAht20Temp();     // I2C 总线篇的实现

    JsonDocument doc;
    doc["temp"] = temp;
    String body;
    serializeJson(doc, body);

    HTTPClient http;
    http.begin("http://api.example.com/report");
    http.addHeader("Content-Type", "application/json");
    http.POST(body);
    http.end();
  }
}

六、避坑速查表(收藏级)

症状根因与对策
搜不到 5GHz 热点仅支持 2.4GHz,正常现象
连接 15 秒超时路由器 5G 频段/中文 SSID/密码错;用手机热点先验证
反复 begin 反而连不上begin 只调一次,交给自动重连
HTTP 返回 -1未连网就请求;或域名 DNS 失败,检查 WiFi.status()
http.end() 忘写连接泄漏,几次后 new 连接失败
偶发 Brownout 重启射频瞬态电流 ~450mA,见选型指南电源陷阱
JSON 「out of memory」v6 手动容量给小了;升 v7 或加大
上电卡 30 秒才连上NVS 存了旧配置 + 阻塞等待;改事件驱动

七、延伸阅读

  • ⏭️ 下一篇:WebServer 与 mDNS——让手机浏览器直接控制 ESP32
  • 🧭 地基:定时器——非阻塞周期上报
  • 📚 深入方向:MQTT(L3)、ESP-NOW(L3)、深度睡眠低功耗上报(L3)

版权声明:本文采用 CC BY-SA 4.0 协议授权。转载与二创请注明出处:ESPDIY 创客工坊 - espdiy.com

ESP32 Wi-Fi First Steps: STA/AP Modes, Connection Management and HTTP

Milestone: at this point you command GPIO, ADC, PWM and the buses for local peripherals. Bring Wi-Fi up and the ESP32 graduates from “microcontroller” to “IoT device” — data reaches the cloud, phones reach the device.


1. The three operating modes

ModeRoleUse case
STA (Station)Client that joins a routerDevices fetching/reporting data
AP (Access Point)Emits its own hotspotConfig portal without a router (phone connects to the device)
AP+STABoth at onceThe classic provisioning architecture: AP collects config → STA joins the router
WiFi.mode(WIFI_STA);              // client mode (most common)
WiFi.mode(WIFI_AP);               // hotspot mode
WiFi.mode(WIFI_AP_STA);           // dual mode

The classic ESP32 is 2.4GHz-only — not finding your 5GHz network is normal, not a fault.


2. Connecting: blocking vs event-driven

1. Blocking version (fine for prototypes)

#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");

  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.printf("\nConnected! IP: %s\n", WiFi.localIP().toString().c_str());
}

Problem: if it can’t connect, it waits forever (remember the watchdog from timers?).

2. Event-driven version (production posture)

WiFiEventHandler onGotIP, onDisconnect;

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);            // automatic reconnection
  WiFi.persistent(false);                 // don't wear the flash with credentials

  onGotIP = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &e) {
    Serial.printf("Connected, IP: %s\n", e.ip.toString().c_str());
  });
  onDisconnect = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected &e) {
    Serial.println("Disconnected, auto-reconnecting...");
  });

  WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
}

Key point: never call WiFi.begin() repeatedly in loop — every call restarts the radio and makes things worse. begin once + auto-reconnect + event callbacks is the standard architecture.


3. HTTP: GET and POST

#include <WiFi.h>
#include <HTTPClient.h>

void fetchWeather() {
  HTTPClient http;
  http.begin("http://api.example.com/weather?city=shenzhen");
  int code = http.GET();

  if (code == 200) {
    String payload = http.getString();
    Serial.println(payload);
  }
  http.end();                            // release the connection (mandatory!)
}

void postData() {
  HTTPClient http;
  http.begin("http://api.example.com/log");
  http.addHeader("Content-Type", "application/json");
  int code = http.POST("{\"temp\":25.5}");
  Serial.printf("POST status: %d\n", code);
  http.end();
}

HTTPS: just switch the URL to https:// (root certificates are built in; some self-signed services need config). Production code should be HTTPS-only.


4. ArduinoJson: parsing responses

#include <ArduinoJson.h>

void parseWeather(String json) {
  JsonDocument doc;                          // v7 auto-capacity
  DeserializationError err = deserializeJson(doc, json);
  if (err) { Serial.println("JSON parse failed"); return; }

  const char* city  = doc["city"];
  float temp        = doc["temperature"];
  int humidity      = doc["humidity"];

  Serial.printf("%s: %.1f°C, humidity %d%%\n", city, temp, humidity);
}

v6 and v7 differ (StaticJsonDocument/DynamicJsonDocument vs JsonDocument) — “not declared” errors mean you hit the version watershed, same story as LEDC.


5. Project: the full temperature reporting loop

Wiring together everything from this column (an L2 acceptance project):

// Architecture: timer(collect) → I2C(read AHT20) → Wi-Fi(HTTP report) → non-blocking loop
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

uint32_t tReport = 0;

void loop() {
  if (WiFi.status() == WL_CONNECTED && millis() - tReport > 60000) {
    tReport = millis();
    float temp = readAht20Temp();     // from the buses guide

    JsonDocument doc;
    doc["temp"] = temp;
    String body;
    serializeJson(doc, body);

    HTTPClient http;
    http.begin("http://api.example.com/report");
    http.addHeader("Content-Type", "application/json");
    http.POST(body);
    http.end();
  }
}

6. Pitfall cheat sheet (bookmark this)

SymptomCause and fix
Can’t see the 5GHz network2.4GHz-only — normal
15-second connect timeout5GHz SSID / non-ASCII SSID / wrong password; verify with a phone hotspot
Repeated begin makes it worsebegin once; leave reconnecting to auto-reconnect
HTTP returns -1Requested while offline; or DNS failure — check WiFi.status()
Forgot http.end()Connection leak — later requests fail
Random Brownout rebootsRadio transients ~450mA — power traps in the Chip Selection Guide
JSON “out of memory”v6 manual capacity too small; upgrade to v7 or enlarge
30s stall on bootStale NVS config + blocking wait; go event-driven

7. Further reading

  • ⏭️ Next: WebServer & mDNS — control the ESP32 from a phone browser
  • 🧭 Foundation: Timers — non-blocking periodic reporting
  • 📚 Going deeper: MQTT (L3), ESP-NOW (L3), deep-sleep low-power reporting (L3)

License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com