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?
- 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ộ.
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ị
- ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) và cáp USB có truyền dữ liệu cho lần nạp đầu.
- Mạng Wi-Fi 2,4 GHz; máy tính kết nối cùng mạng đó.
- Arduino IDE 2.x và gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Thư viện
ArduinoOTAcó sẵn trong gói này.
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
- Sửa một chỗ dễ nhận biết trong chương trình, ví dụ đổi
CHU_KY_NHAY_MStừ 1000 thành 200. - 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.
- Bấm Upload. Arduino IDE hỏi mật khẩu: nhập mật khẩu OTA đã đặt trong chương trình.
- 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.
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.
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ơ trongonStart().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 trongloop(), vì vậy phần nháy LED dùngmillis()thay chodelay().
6. Ba quy tắc để không mất khả năng nạp qua Wi-Fi
- 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. - Không chặn
loop(). Lệnhdelay()dài hoặc vòng lặp chờ vô hạn làmArduinoOTA.handle()không được gọi, Arduino IDE sẽ báo không kết nối được. - 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_KHAUcủ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ênArduinoOTA.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.
Xem thêm