Hướng dẫn kết nối Wi-Fi trên ESP32: quét mạng, tự kết nối lại, IP tĩnh và phát Wi-Fi riêng

Hướng dẫn các thao tác Wi-Fi cần dùng nhất trên ESP32 với Arduino IDE: quét mạng, kết nối có thời gian chờ, xem IP và cường độ sóng, tự kết nối lại, đặt IP tĩnh, phát Wi-Fi riêng.

Hầu hết dự án IoT với ESP32 đều bắt đầu bằng việc kết nối Wi-Fi, và phần lớn lỗi khó hiểu của người mới cũng nằm ở bước này. Bài viết gom những thao tác Wi-Fi cần dùng nhất trên ESP32 với Arduino IDE: quét mạng, kết nối có thời gian chờ, xem địa chỉ IP và cường độ sóng, tự kết nối lại khi rớt mạng, đặt IP tĩnh và cho ESP32 tự phát một mạng Wi-Fi riêng.

1. Hai chế độ Wi-Fi của ESP32

Hai chế độ Wi-Fi của ESP32: chế độ trạm kết nối vào router để ra Internet và chế độ điểm phát cho điện thoại kết nối trực tiếp vào ESP32
Chế độ trạm và chế độ điểm phát của ESP32 (hình vẽ minh họa).
  • Chế độ trạm (Station, WIFI_STA): ESP32 kết nối vào mạng Wi-Fi có sẵn và được router cấp một địa chỉ IP. Đây là chế độ dùng khi cần Internet.
  • Chế độ điểm phát (Access Point, WIFI_AP): ESP32 tự phát một mạng Wi-Fi để điện thoại kết nối thẳng vào. Mạng này không có Internet, phù hợp để cấu hình thiết bị hoặc điều khiển tại chỗ.
  • Cả hai cùng lúc (WIFI_AP_STA): vừa kết nối router vừa phát mạng riêng.

ESP32 chỉ làm việc với Wi-Fi băng tần 2,4 GHz. Nếu router phát chung một tên cho cả 2,4 GHz và 5 GHz thì ESP32 vẫn kết nối được vào phần 2,4 GHz.

2. Linh kiện cần chuẩn bị

3. Chương trình 1: quét các mạng Wi-Fi xung quanh

Quét mạng là cách nhanh nhất để biết ESP32 có "nhìn thấy" mạng nhà bạn hay không và sóng mạnh đến đâu:

/*
  PYWORLD.VN - ESP32 quét các mạng Wi-Fi xung quanh
  Chỉ cần mạch ESP32
  Không cần cài thêm thư viện
*/

#include <WiFi.h>

const char *tenBaoMat(wifi_auth_mode_t kieu) {
  switch (kieu) {
    case WIFI_AUTH_OPEN:          return "Mo";
    case WIFI_AUTH_WEP:           return "WEP";
    case WIFI_AUTH_WPA_PSK:       return "WPA";
    case WIFI_AUTH_WPA2_PSK:      return "WPA2";
    case WIFI_AUTH_WPA_WPA2_PSK:  return "WPA/WPA2";
    case WIFI_AUTH_WPA3_PSK:      return "WPA3";
    case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2/WPA3";
    default:                      return "Khac";
  }
}

void setup() {
  Serial.begin(115200);
  delay(500);

  WiFi.mode(WIFI_STA);   // Chế độ trạm: ESP32 đóng vai một thiết bị Wi-Fi
  WiFi.disconnect();
}

void loop() {
  Serial.println("Dang quet mang WiFi...");
  int soMang = WiFi.scanNetworks();   // Mất vài giây

  if (soMang <= 0) {
    Serial.println("Khong thay mang nao.");
  } else {
    Serial.printf("Thay %d mang:\n", soMang);
    Serial.println(" STT  RSSI  Kenh  Bao mat    Ten mang");
    for (int i = 0; i < soMang; i++) {
      Serial.printf(" %3d  %4d  %4d  %-9s  %s\n", i + 1, (int)WiFi.RSSI(i), (int)WiFi.channel(i),
                    tenBaoMat(WiFi.encryptionType(i)), WiFi.SSID(i).c_str());
    }
  }
  WiFi.scanDelete();   // Giải phóng bộ nhớ chứa kết quả quét
  Serial.println();
  delay(10000);
}

Chọn board DOIT ESP32 DEVKIT V1, nạp chương trình rồi mở Serial Monitor ở 115200 baud:

Hình minh họa Serial Monitor liệt kê các mạng Wi-Fi ESP32 quét được kèm RSSI, kênh và kiểu bảo mật
Kết quả mong đợi khi quét mạng (hình minh họa theo định dạng in của chương trình, tên mạng là ví dụ).

Cột RSSI cho biết cường độ sóng mà ESP32 thu được từ từng mạng:

Thang cường độ sóng Wi-Fi theo RSSI: từ âm 30 đến âm 50 dBm rất mạnh, đến dưới âm 80 dBm rất yếu
Cách đọc cường độ sóng theo RSSI (hình vẽ minh họa, các mốc mang tính tham khảo).

Nếu mạng của bạn không có trong danh sách, nguyên nhân thường gặp là router chỉ phát 5 GHz, mạng bị ẩn tên, hoặc mạch đặt quá xa.

4. Chương trình 2: kết nối, xem thông tin mạng và theo dõi rớt mạng

Sửa TEN_WIFI và MAT_KHAU_WIFI rồi nạp chương trình. So với cách chờ kết nối mãi mãi, chương trình này có thời gian chờ 20 giây và in lý do khi mất kết nối:

/*
  PYWORLD.VN - ESP32 kết nối Wi-Fi: có thời gian chờ, in thông tin mạng, báo khi mất kết nối
  Chỉ cần mạch ESP32 và mạng Wi-Fi 2,4 GHz
  Không cần cài thêm thư viện
*/

#include <WiFi.h>

const char *WIFI_SSID = "TEN_WIFI";
const char *WIFI_PASS = "MAT_KHAU_WIFI";
const unsigned long THOI_GIAN_CHO_MS = 20000;

// Muốn dùng IP tĩnh: bỏ dấu // ở bốn dòng dưới và ở dòng WiFi.config() trong hàm ketNoiWiFi()
// IPAddress IP_TINH(192, 168, 1, 150);
// IPAddress CONG_MANG(192, 168, 1, 1);
// IPAddress MAT_NA(255, 255, 255, 0);
// IPAddress DNS_1(8, 8, 8, 8);

// Hàm này được gọi mỗi khi trạng thái Wi-Fi thay đổi
void khiCoSuKienWiFi(WiFiEvent_t suKien, WiFiEventInfo_t thongTin) {
  switch (suKien) {
    case ARDUINO_EVENT_WIFI_STA_GOT_IP:
      Serial.println("Su kien: da ket noi va co dia chi IP.");
      break;
    case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
      Serial.printf("Su kien: mat ket noi WiFi (ma ly do %d). ESP32 se tu ket noi lai.\n",
                    (int)thongTin.wifi_sta_disconnected.reason);
      break;
    default:
      break;
  }
}

bool ketNoiWiFi() {
  Serial.printf("Dang ket noi WiFi %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.setHostname("esp32-pyworld");   // Tên thiết bị hiện trong danh sách của router
  // WiFi.config(IP_TINH, CONG_MANG, MAT_NA, DNS_1);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  unsigned long batDau = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - batDau > THOI_GIAN_CHO_MS) {
      Serial.println("\nQua thoi gian cho. Kiem tra ten va mat khau WiFi.");
      return false;
    }
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  return true;
}

void inThongTinMang() {
  Serial.printf("Dia chi IP   : %s\n", WiFi.localIP().toString().c_str());
  Serial.printf("Cong mang    : %s\n", WiFi.gatewayIP().toString().c_str());
  Serial.printf("Dia chi MAC  : %s\n", WiFi.macAddress().c_str());
  Serial.printf("Kenh         : %d\n", (int)WiFi.channel());
  Serial.printf("Cuong do song: %d dBm\n", (int)WiFi.RSSI());
}

void setup() {
  Serial.begin(115200);
  delay(500);

  WiFi.onEvent(khiCoSuKienWiFi);
  if (ketNoiWiFi()) {
    inThongTinMang();
  }
}

void loop() {
  // Cứ 10 giây in cường độ sóng một lần
  static unsigned long lanTruoc = 0;
  if (millis() - lanTruoc >= 10000) {
    lanTruoc = millis();
    if (WiFi.status() == WL_CONNECTED) {
      Serial.printf("Cuong do song: %d dBm\n", (int)WiFi.RSSI());
    } else {
      Serial.println("Chua co ket noi WiFi.");
    }
  }
}
Hình minh họa Serial Monitor khi ESP32 kết nối Wi-Fi: địa chỉ IP, cổng mạng, địa chỉ MAC, kênh và cường độ sóng
Kết quả mong đợi khi kết nối thành công (hình minh họa; địa chỉ IP và MAC là giá trị ví dụ).

Thử tắt router vài giây rồi bật lại: Serial Monitor in dòng báo mất kết nối, sau đó ESP32 tự kết nối lại và in dòng báo đã có địa chỉ IP.

5. Giải thích các lệnh chính

  • WiFi.mode(WIFI_STA): chọn chế độ trạm.
  • WiFi.setHostname("esp32-pyworld"): đặt tên thiết bị để dễ tìm trong danh sách của router. Phải gọi trước WiFi.begin().
  • WiFi.begin(tên mạng, mật khẩu): bắt đầu kết nối. Lệnh trả về ngay; chương trình kiểm tra WiFi.status() cho đến khi bằng WL_CONNECTED hoặc hết thời gian chờ.
  • WiFi.localIP(), WiFi.gatewayIP(), WiFi.macAddress(), WiFi.RSSI(): địa chỉ IP của ESP32, địa chỉ của router, địa chỉ MAC và cường độ sóng hiện tại.
  • WiFi.onEvent(khiCoSuKienWiFi): đăng ký một hàm được gọi mỗi khi trạng thái Wi-Fi thay đổi. Bài dùng hai sự kiện: đã có địa chỉ IP và mất kết nối.
  • Tự kết nối lại: ESP32 mặc định tự thử kết nối lại khi rớt mạng, không cần viết thêm lệnh. Chương trình chính chỉ cần kiểm tra WiFi.status() trước khi gửi dữ liệu.

6. Đặt địa chỉ IP tĩnh

Mặc định router cấp cho ESP32 một địa chỉ IP có thể thay đổi sau mỗi lần khởi động. Khi ESP32 làm web server, nên giữ địa chỉ cố định để khỏi phải dò lại. Trong chương trình 2, bỏ dấu // ở bốn dòng khai báo và ở dòng WiFi.config():

IPAddress IP_TINH(192, 168, 1, 150);
IPAddress CONG_MANG(192, 168, 1, 1);
IPAddress MAT_NA(255, 255, 255, 0);
IPAddress DNS_1(8, 8, 8, 8);

WiFi.config(IP_TINH, CONG_MANG, MAT_NA, DNS_1);   // Gọi trước WiFi.begin()
  • Ba số đầu của địa chỉ phải trùng với mạng của bạn. Chạy chương trình 2 một lần ở chế độ thường để xem địa chỉ IP và cổng mạng router đang cấp.
  • Chọn số cuối nằm ngoài dải router tự cấp, hoặc dùng chức năng giữ địa chỉ theo MAC (DHCP reservation) của router. Cách thứ hai an toàn hơn vì không lo trùng địa chỉ.
  • Phải khai báo DNS thì ESP32 mới truy cập được tên miền như pool.ntp.org.

7. Chương trình 3: ESP32 tự phát Wi-Fi

Ở nơi không có router, ESP32 có thể tự phát một mạng Wi-Fi để điện thoại kết nối trực tiếp:

/*
  PYWORLD.VN - ESP32 tự phát Wi-Fi (chế độ Access Point) và trả về một trang web nhỏ
  Kết nối điện thoại vào mạng ESP32-PYWORLD rồi mở trình duyệt tại http://192.168.4.1
  Không cần cài thêm thư viện
*/

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

const char *AP_SSID = "ESP32-PYWORLD";
const char *AP_PASS = "doi-mat-khau";   // Tối thiểu 8 ký tự; hãy đổi thành mật khẩu của bạn

WebServer server(80);

void trangChu() {
  String html = "<!DOCTYPE html><html lang=\"vi\"><head><meta charset=\"utf-8\">"
                "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                "<title>ESP32</title></head><body style=\"font-family:Arial;text-align:center\">"
                "<h1>Xin chào từ ESP32</h1><p>Số thiết bị đang kết nối: ";
  html += WiFi.softAPgetStationNum();
  html += "</p></body></html>";
  server.send(200, "text/html; charset=utf-8", html);
}

void setup() {
  Serial.begin(115200);
  delay(500);

  WiFi.mode(WIFI_AP);   // Chế độ điểm phát: ESP32 tự tạo một mạng Wi-Fi
  if (!WiFi.softAP(AP_SSID, AP_PASS)) {
    Serial.println("Khong phat duoc WiFi! Mat khau phai dai it nhat 8 ky tu.");
    while (true) delay(1000);
  }
  Serial.printf("Dang phat WiFi %s\n", AP_SSID);
  Serial.printf("Mo trinh duyet tai dia chi: http://%s\n", WiFi.softAPIP().toString().c_str());

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

void loop() {
  server.handleClient();   // Xử lý yêu cầu từ trình duyệt
}
  1. Nạp chương trình, mở phần cài đặt Wi-Fi của điện thoại và kết nối vào mạng ESP32-PYWORLD bằng mật khẩu đã đặt trong chương trình.
  2. Điện thoại có thể báo mạng không có Internet: chọn vẫn giữ kết nối.
  3. Mở trình duyệt, vào địa chỉ http://192.168.4.1. Trang hiện dòng chào và số thiết bị đang kết nối.

Địa chỉ mặc định của ESP32 ở chế độ điểm phát là 192.168.4.1. Mật khẩu phải dài ít nhất 8 ký tự; nếu ngắn hơn, lệnh WiFi.softAP() thất bại.

8. Một số mẹo khi làm dự án thật

  • Không để chương trình treo ở bước kết nối: luôn có thời gian chờ như chương trình 2. Thiết bị cần chạy cả khi mất mạng thì bỏ qua phần mạng và thử lại sau.
  • Giảm độ trễ: mặc định Wi-Fi có chế độ tiết kiệm điện làm phản hồi chậm vài chục mili giây. Với web server hoặc điều khiển cần nhanh, gọi WiFi.setSleep(false) sau khi kết nối.
  • Nguồn: lúc phát sóng ESP32 có thể cần dòng đỉnh vài trăm mA. Cáp USB kém hoặc nguồn yếu làm mạch tự khởi động lại đúng lúc kết nối Wi-Fi.
  • Anten: không đặt phần anten của module sát kim loại hay trong hộp kim loại kín.
  • Chân analog: khi bật Wi-Fi, các chân thuộc nhóm ADC2 không đọc được analog; dùng GPIO32–39.

9. Xử lý lỗi thường gặp

  • Báo "Qua thoi gian cho": sai tên hoặc mật khẩu (có phân biệt chữ hoa, chữ thường), router chỉ phát 5 GHz, hoặc sóng quá yếu. Chạy chương trình quét mạng để kiểm tra.
  • Kết nối được nhưng không ra Internet khi dùng IP tĩnh: sai cổng mạng hoặc thiếu DNS trong WiFi.config().
  • Hai thiết bị cùng lúc chập chờn: trùng địa chỉ IP tĩnh. Đổi số cuối của một thiết bị.
  • ESP32 khởi động lại liên tục khi bật Wi-Fi, Serial Monitor có chữ "Brownout": nguồn sụt áp. Đổi cáp USB, đổi cổng USB hoặc cấp nguồn ngoài ổn định.
  • Rớt mạng thường xuyên: xem cường độ sóng; dưới khoảng −75 dBm nên dời mạch lại gần router hoặc dùng bản có anten rời.
  • Không vào được 192.168.4.1: điện thoại đã tự chuyển sang mạng khác vì mạng của ESP32 không có Internet. Tắt dữ liệu di động và kết nối lại vào ESP32-PYWORLD.
  • Không nạp được chương trình: dùng cáp USB có truyền dữ liệu, chọn đúng cổng COM và giữ nút BOOT khi IDE dừng ở dòng Connecting...

Xem thêm: ESP32 làm web server điều khiển relay qua Wi-Fi, bắt đầu với Wemos D1 R2 (ESP8266) trên Arduino IDE và nạp file .bin vào ESP32 bằng Flash Download Tool.


Tin tức liên quan

Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE
Hướng dẫn làm cân điện tử với ESP32, HX711 và load cell trên Arduino IDE

7 Lượt xem

Hướng dẫn nối load cell với module HX711 và ESP32 DevKit V1, gá load cell đúng cách, hiệu chuẩn bằng vật đã biết khối lượng và viết chương trình cân trên Arduino IDE.

Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch
Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch

5 Lượt xem

Hướng dẫn ESP32 lấy giờ Việt Nam từ Internet bằng NTP trên Arduino IDE, in ra Serial Monitor và làm đồng hồ hiển thị thứ, ngày, giờ trên màn hình OLED 0.96 inch SSD1306.

Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient
Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient

5 Lượt xem

Hướng dẫn dùng MQTT với ESP32 trên Arduino IDE: broker, topic, publish, subscribe, cài Mosquitto và PubSubClient, gửi dữ liệu và điều khiển module relay qua mạng nội bộ.

Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32
Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32

5 Lượt xem

Hướng dẫn dùng Bluetooth Classic của ESP32 trên Arduino IDE: ghép đôi với điện thoại Android, gửi nhận chữ bằng BluetoothSerial và điều khiển module relay bằng lệnh ON, OFF.

Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE
Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE

4 Lượt xem

Hướng dẫn bật OTA trên ESP32 bằng thư viện ArduinoOTA có sẵn, nạp chương trình qua Wi-Fi từ Arduino IDE 2.x, đặt mật khẩu và các quy tắc để không mất khả năng cập nhật từ xa.

Hướng dẫn dùng màn hình OLED 0.96 inch SSD1306 với Arduino Nano
Hướng dẫn dùng màn hình OLED 0.96 inch SSD1306 với Arduino Nano

5 Lượt xem

Hướng dẫn nối màn hình OLED 0.96 inch SSD1306 (I2C) với Arduino Nano, cài thư viện Adafruit, hiện chữ nhiều cỡ, vẽ thanh mức theo điện áp và lưu ý về bộ nhớ RAM.

Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp
Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp

5 Lượt xem

Hướng dẫn đọc điện áp analog bằng ADC của ESP32 trên Arduino IDE, dùng analogReadMilliVolts, làm cầu phân áp đo pin Li-ion 18650 và ước lượng phần trăm pin còn lại.

Hướng dẫn dùng Deep Sleep trên ESP32: tiết kiệm pin, đánh thức bằng timer và nút nhấn
Hướng dẫn dùng Deep Sleep trên ESP32: tiết kiệm pin, đánh thức bằng timer và nút nhấn

5 Lượt xem

Hướng dẫn đưa ESP32 vào Deep Sleep trên Arduino IDE để tiết kiệm pin: các chế độ nguồn, đánh thức bằng timer và nút nhấn, giữ biến trong bộ nhớ RTC và ước tính thời lượng pin.


Bình luận
Đã thêm vào giỏ hàng