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

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.

Khi mạch ESP32 đã lắp vào hộp, gắn trên tường hay đặt ngoài vườn, mỗi lần sửa chương trình mà phải tháo ra cắm cáp USB thì rất bất tiện. OTA (Over-The-Air) cho phép nạp chương trình mới qua chính mạng Wi-Fi mà ESP32 đang kết nối. Bài viết hướng dẫn bật OTA bằng thư viện ArduinoOTA có sẵn, nạp chương trình qua Wi-Fi từ Arduino IDE 2.x và những điều cần nhớ để không tự "khóa" mạch của mình.

1. OTA hoạt động thế nào?

Quy trình nạp OTA cho ESP32: lần đầu nạp bằng cáp USB chương trình có sẵn phần OTA, các lần sau máy tính gửi chương trình mới qua router Wi-Fi
Hai giai đoạn của việc nạp chương trình qua Wi-Fi (hình vẽ minh họa).
  • Lần đầu vẫn cần cáp USB để nạp một chương trình có sẵn phần OTA cùng tên và mật khẩu Wi-Fi.
  • Từ lần sau, ESP32 lắng nghe trên mạng. Arduino IDE tìm thấy nó như một cổng mạng và gửi chương trình mới qua Wi-Fi.
  • Máy tính và ESP32 phải ở cùng một mạng nội bộ.
Sơ đồ bộ nhớ flash 4 MB của ESP32 với hai vùng chương trình app0 và app1 mỗi vùng 1,25 MB dùng cho OTA và vùng dữ liệu
Bộ nhớ flash của ESP32 có hai vùng chương trình để cập nhật an toàn (hình vẽ theo bảng phân vùng mặc định).

Chương trình mới được ghi vào vùng đang rảnh trong lúc chương trình cũ vẫn chạy. Chỉ khi nhận đủ và kiểm tra xong, ESP32 mới khởi động lại bằng vùng mới. Dữ liệu lưu bằng thư viện Preferences và các file trong vùng dữ liệu không bị ảnh hưởng.

2. Linh kiện và phần mềm cần chuẩn bị

3. Chương trình có sẵn OTA

Sửa tên và mật khẩu Wi-Fi, đặt mật khẩu OTA của riêng bạn rồi nạp chương trình bằng cáp USB như bình thường (board DOIT ESP32 DEVKIT V1):

/*
  PYWORLD.VN - ESP32 nhận chương trình mới qua Wi-Fi (OTA) từ Arduino IDE
  Lần đầu nạp bằng cáp USB; các lần sau chọn cổng mạng esp32-pyworld trong Tools > Port
  LED trên mạch (GPIO2) nháy để nhận biết phiên bản đang chạy
  Thư viện ArduinoOTA có sẵn trong gói esp32, không cần cài thêm
*/

#include <WiFi.h>
#include <ArduinoOTA.h>

const char *WIFI_SSID = "TEN_WIFI";
const char *WIFI_PASS = "MAT_KHAU_WIFI";

const char *OTA_TEN = "esp32-pyworld";        // Tên hiện trong danh sách cổng mạng của Arduino IDE
const char *OTA_MAT_KHAU = "doi-mat-khau";    // Arduino IDE hỏi mật khẩu này khi nạp qua Wi-Fi

#define LED_PIN 2
const unsigned long CHU_KY_NHAY_MS = 1000;    // Đổi thành 200 rồi nạp qua Wi-Fi để thấy LED nháy nhanh hơn

void ketNoiWiFi() {
  Serial.printf("Dang ket noi WiFi %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  unsigned long batDau = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - batDau > 20000) {
      Serial.println("\nKhong ket noi duoc WiFi. Khoi dong lai sau 5 giay...");
      delay(5000);
      ESP.restart();
    }
    delay(500);
    Serial.print(".");
  }
  Serial.println();
}

void caiDatOTA() {
  ArduinoOTA.setHostname(OTA_TEN);
  ArduinoOTA.setPassword(OTA_MAT_KHAU);

  ArduinoOTA.onStart([]() {
    Serial.println("Bat dau nhan chuong trinh moi...");
  });
  ArduinoOTA.onProgress([](unsigned int daNhan, unsigned int tong) {
    Serial.printf("Tien do: %u %%\r", daNhan * 100 / tong);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nNhan xong, khoi dong lai.");
  });
  ArduinoOTA.onError([](ota_error_t loi) {
    Serial.printf("Loi OTA, ma %u\n", (unsigned int)loi);
  });

  ArduinoOTA.begin();
}

void setup() {
  Serial.begin(115200);
  delay(500);
  pinMode(LED_PIN, OUTPUT);

  ketNoiWiFi();
  caiDatOTA();
  Serial.printf("San sang nhan chuong trinh qua WiFi. Ten: %s, IP: %s\n", OTA_TEN, WiFi.localIP().toString().c_str());
}

void loop() {
  ArduinoOTA.handle();   // Phải được gọi thường xuyên; không dùng delay() dài trong loop()

  // Phần việc của chương trình: nháy LED theo chu kỳ, không dùng delay()
  static unsigned long lanTruoc = 0;
  if (millis() - lanTruoc >= CHU_KY_NHAY_MS) {
    lanTruoc = millis();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  }
}

Mở Serial Monitor ở 115200 baud. Khi thấy dòng San sang nhan chuong trinh qua WiFi kèm địa chỉ IP, LED trên mạch nháy mỗi giây một lần: ESP32 đã sẵn sàng nhận chương trình qua mạng.

4. Nạp chương trình mới qua Wi-Fi

  1. Sửa một chỗ dễ nhận biết trong chương trình, ví dụ đổi CHU_KY_NHAY_MS từ 1000 thành 200.
  2. Mở Tools > Port. Dưới mục Network ports có dòng esp32-pyworld at <địa chỉ IP>; chọn dòng đó. Nếu chưa thấy, chờ khoảng nửa phút hoặc khởi động lại Arduino IDE.
  3. Bấm Upload. Arduino IDE hỏi mật khẩu: nhập mật khẩu OTA đã đặt trong chương trình.
  4. Chờ thanh tiến độ chạy hết. ESP32 tự khởi động lại và LED nháy nhanh hơn: chương trình mới đã chạy.
Hình minh họa menu Tools Port của Arduino IDE với cổng mạng esp32-pyworld at 192.168.1.50 và ba bước nạp qua Wi-Fi
Chọn cổng mạng của ESP32 thay cho cổng COM (hình minh họa bố cục menu, địa chỉ là ví dụ).

Lần đầu nạp qua mạng, Windows có thể hỏi quyền truy cập mạng cho Arduino IDE: chọn cho phép trên mạng riêng. Trong lúc đang chọn cổng mạng, Serial Monitor của Arduino IDE không mở được; muốn xem lại Serial Monitor thì chọn lại cổng COM.

Hình minh họa các dòng ESP32 in ra khi nhận chương trình mới qua Wi-Fi: bắt đầu nhận, tiến độ 100 phần trăm, nhận xong và khởi động lại
Các dòng ESP32 in ra cổng nối tiếp trong lúc nhận chương trình (hình minh họa theo định dạng in của chương trình).

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

  • ArduinoOTA.setHostname(OTA_TEN): tên thiết bị hiện trong danh sách cổng mạng. Mỗi mạch nên có một tên riêng.
  • ArduinoOTA.setPassword(OTA_MAT_KHAU): đặt mật khẩu. Không có lệnh này, bất kỳ máy nào trong mạng cũng nạp được chương trình vào mạch.
  • ArduinoOTA.onStart(), onProgress(), onEnd(), onError(): các hàm được gọi khi bắt đầu, trong lúc nhận, khi nhận xong và khi có lỗi. Ở đây chúng chỉ in thông báo; trong dự án thật nên tắt relay, dừng động cơ trong onStart().
  • ArduinoOTA.begin(): bắt đầu lắng nghe. Phải gọi sau khi đã kết nối Wi-Fi.
  • ArduinoOTA.handle(): kiểm tra xem có yêu cầu nạp hay không. Lệnh này phải được gọi liên tục trong loop(), vì vậy phần nháy LED dùng millis() thay cho delay().

6. Ba quy tắc để không mất khả năng nạp qua Wi-Fi

  1. Chương trình nào nạp qua OTA cũng phải có phần OTA. Nếu nạp một chương trình thiếu ArduinoOTA.begin() và ArduinoOTA.handle(), mạch sẽ không nhận được lần cập nhật kế tiếp và phải quay lại dùng cáp USB.
  2. Không chặn loop(). Lệnh delay() dài hoặc vòng lặp chờ vô hạn làm ArduinoOTA.handle() không được gọi, Arduino IDE sẽ báo không kết nối được.
  3. Chương trình phải kết nối được Wi-Fi. Sai tên hoặc mật khẩu Wi-Fi trong bản mới thì mạch không lên mạng được nữa. Kiểm tra kỹ phần này trước khi bấm Upload.

7. Lưu ý khi dùng thực tế

  • Kích thước chương trình: mỗi vùng chương trình chỉ khoảng 1,25 MB. Bảng phân vùng không có hai vùng chương trình (như Huge APP) thì không dùng được OTA.
  • Nguồn điện: không ngắt nguồn trong lúc đang nhận. Nếu bị đứt giữa chừng, ESP32 vẫn chạy chương trình cũ, chỉ cần nạp lại.
  • Bảo mật: mật khẩu OTA chỉ chống nạp nhầm trong mạng nội bộ. Không mở cổng của thiết bị ra Internet.
  • Thiết bị chạy pin dùng Deep Sleep: lúc ngủ ESP32 không nghe mạng. Cần cho mạch thức đủ lâu, ví dụ giữ một nút nhấn khi khởi động để vào chế độ chờ cập nhật.
  • Địa chỉ IP thay đổi: Arduino IDE tìm thiết bị theo tên nên không cần IP cố định, nhưng đặt IP tĩnh giúp cổng mạng hiện ra ổn định hơn.

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

  • Không thấy mục Network ports: máy tính và ESP32 không cùng mạng (ví dụ máy tính dùng mạng dây khác lớp, hoặc router bật chế độ cách ly thiết bị Wi-Fi); tường lửa chặn Arduino IDE; hoặc ESP32 chưa kết nối được Wi-Fi. Xem Serial Monitor qua cáp USB để kiểm tra.
  • Báo sai mật khẩu: mật khẩu nhập phải trùng với OTA_MAT_KHAU của chương trình đang chạy trên mạch, không phải của chương trình sắp nạp. Arduino IDE nhớ mật khẩu đã nhập cho đến khi được khởi động lại.
  • Báo không có phản hồi từ thiết bị: loop() đang bị chặn nên ArduinoOTA.handle() không chạy, hoặc sóng Wi-Fi quá yếu.
  • Nạp tới giữa chừng thì lỗi: sóng yếu hoặc nguồn không ổn định. Đưa mạch lại gần router và thử lại.
  • Báo chương trình quá lớn: vượt quá kích thước một vùng chương trình. Bỏ bớt thư viện không dùng.
  • Đã lỡ nạp chương trình không có OTA: nạp lại bằng cáp USB một chương trình có phần OTA.

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


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 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.

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 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

5 Lượt xem

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.


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