Điều khiển biến tần qua Modbus RTU bằng ESP32: nối RS-485, cài tham số, thanh ghi lệnh và tần số
Điều khiển biến tần qua Modbus RTU từ ESP32 và MAX485: nối dây RS-485, cài nguồn lệnh và tần số, bảng thanh ghi Delta VFD-E, khung tin có CRC, chạy, dừng, đặt tần số, đọc dòng và mã lỗi.
Hầu hết biến tần công nghiệp có cổng RS-485 và hiểu giao thức Modbus RTU. Thay vì nối dây FWD, REV và biến trở tần số, chỉ cần một cặp dây là ESP32 (hoặc PLC, máy tính) ra lệnh chạy, dừng, đổi chiều, đặt tần số, đồng thời đọc lại tần số ra, dòng điện và mã lỗi. Bài này hướng dẫn điều khiển biến tần qua Modbus từ ESP32: nối dây qua module MAX485, cài tham số trên biến tần, đọc bảng thanh ghi và một chương trình hoàn chỉnh. Bảng thanh ghi lấy theo biến tần Delta VFD-E; hãng khác chỉ cần đổi vài hằng số.
Biến tần và cách cài tham số cơ bản có ở bài cùng cụm về biến tần (cấu tạo, đường V/f, tham số cơ bản). Nền tảng Modbus RTU xem ở Modbus RTU (phần 1): khung tin, mã hàm, CRC.
1. Nối ESP32 với biến tần

| Module MAX485 | Nối tới | Ghi chú |
|---|---|---|
| DI | GPIO17 (TX2) | ESP32 phát |
| RO | GPIO16 (RX2) | ESP32 nhận |
| DE, RE (nối chung) | GPIO4 | mức cao: phát; mức thấp: nghe |
| VCC, GND | 3V3 (hoặc 5V tùy module), GND | module 5 V thì RO cần chia áp xuống 3,3 V |
| A, B | SG+ (D+), SG− (D−) của biến tần | cọc vít hoặc cổng RJ45, tra sơ đồ chân trong sách |
- Đoạn dây dài (hàng chục mét trở lên) đặt điện trở 120 Ω ở hai đầu xa nhất của đường dây. Nhiều biến tần có công tắc gạt điện trở đầu cuối ngay trên mạch.
- Biến tần là nguồn nhiễu mạnh: dây RS-485 dùng cáp xoắn đôi có vỏ chống nhiễu, đi riêng và cách xa dây ra động cơ. Trong tủ điện thật nên dùng module RS-485 cách ly.
Chi tiết về cặp dây vi sai, điện trở đầu cuối và module MAX485: RS-485 là gì. ESP32 dùng loại ESP32 DevKit V1 30 chân; trong tủ điện có thể cắm vào đế ra chân ESP32 để bắt vít dây cho chắc.
2. Cài tham số trên biến tần

Mặc định biến tần nhận lệnh từ bàn phím và tần số từ núm vặn. Phải chuyển hai nguồn này sang truyền thông, nếu không biến tần vẫn trả lời các lệnh đọc nhưng bỏ qua lệnh chạy và tần số. Địa chỉ, tốc độ baud và khung dữ liệu (8N2, 8E1…) ghi đúng vào các hằng DIA_CHI_BIEN_TAN, TOC_DO_BAUD, KHUNG_DU_LIEU của chương trình. Khung dữ liệu lệch nhau là lỗi rất hay gặp: biến tần im lặng, không báo gì.
Nên đặt thời gian chờ mất truyền thông (ví dụ 2–3 giây) và cách xử lý là báo lỗi, dừng động cơ. ESP32 bị treo hoặc đứt dây thì động cơ không chạy mãi theo lệnh cuối cùng. Chương trình trong bài đọc trạng thái mỗi 0,5 giây nên luôn có khung tin trên đường dây.
3. Bảng thanh ghi: lệnh, tần số, trạng thái

Với Delta VFD-E, các địa chỉ dùng trong bài:
- 2000H: lệnh (chỉ ghi). 2001H: tần số đặt, đơn vị 0,01 Hz (25,00 Hz ghi 2500). 2002H: bit 1 = 1 để xóa lỗi.
- 2100H: mã lỗi (0 là không lỗi). 2101H: trạng thái, bit 0–1 = 11 là đang chạy, 00 là đã dừng. 2102H: tần số đặt. 2103H: tần số ra. 2104H: dòng ra.
- Tham số của biến tần cũng đọc ghi được qua Modbus: địa chỉ ghép từ nhóm và số tham số, ví dụ tham số 04.01 có địa chỉ 0401H.
Các hãng khác (INVT, Inovance, LS, Siemens, Schneider, Mitsubishi, Fuji…) dùng bảng địa chỉ riêng. Nhiều biến tần giá rẻ có một thanh ghi lệnh nhận số 1 là chạy thuận, 2 là chạy nghịch, 5 hoặc 6 là dừng; tần số đặt tính theo phần trăm tần số lớn nhất (10000 = 100,00 %). Đơn vị của dòng điện, tần số (0,1 hay 0,01) cũng khác nhau theo mẫu. Mở chương Truyền thông trong sách của biến tần, sửa phần BANG THANH GHI ở đầu chương trình cho đúng.
4. Khung tin trên đường dây

Với mã hàm 06, biến tần trả lại đúng khung vừa nhận để xác nhận. Với mã hàm 03, khung trả lời có số byte dữ liệu rồi lần lượt giá trị từng thanh ghi, byte cao trước. Khi có thể, đọc nhiều thanh ghi liền nhau trong một lần như ví dụ thay cho đọc từng thanh ghi: đỡ thời gian trên đường dây.
Có thể dùng các khung trên để thử biến tần bằng phần mềm Modbus trên máy tính qua bộ chuyển USB sang RS-485 trước khi nối ESP32: biến tần phản hồi đúng thì phần cài đặt và dây đã ổn.
5. Chương trình ESP32
Chương trình tự đóng khung tin và tính CRC, không cần thư viện Modbus. Gõ lệnh trong Serial Monitor (115200 baud, chọn kết thúc dòng là New Line): chay, nghich, dung, reset, f 25 (đặt 25 Hz). Mỗi 0,5 giây chương trình đọc 5 thanh ghi trạng thái và in ra.
// PYWORLD.VN - ESP32 điều khiển biến tần qua Modbus RTU (RS-485): chạy thuận, chạy nghịch, dừng, đặt tần số, xóa lỗi; đọc tần số ra, dòng điện, mã lỗi
// PYWORLD.VN - Bảng thanh ghi viết theo biến tần Delta VFD-E (2000H lệnh, 2001H tần số, 2002H xóa lỗi, 2100H–2104H trạng thái); hãng khác sửa phần BANG THANH GHI
// PYWORLD.VN - Module MAX485: DI = GPIO17 (TX2), RO = GPIO16 (RX2), DE và RE = GPIO4; A, B nối cọc SG+, SG- của biến tần. Gõ lệnh trong Serial Monitor 115200
const int RX2_PIN = 16, TX2_PIN = 17, DE_RE_PIN = 4;
const uint8_t DIA_CHI_BIEN_TAN = 1; // trùng tham số địa chỉ truyền thông của biến tần
const uint32_t TOC_DO_BAUD = 9600;
const uint32_t KHUNG_DU_LIEU = SERIAL_8N2; // trùng khung RTU đặt trên biến tần (8N2, 8E1…)
const float TAN_SO_MAX = 50.0; // không cho đặt quá tần số định mức động cơ
// ===== BANG THANH GHI (Delta VFD-E) =====
const uint16_t TG_LENH = 0x2000, TG_TAN_SO = 0x2001, TG_XOA_LOI = 0x2002, TG_TRANG_THAI = 0x2100;
const uint16_t LENH_DUNG = 0x0001; // bit 0-1 = 01: dừng
const uint16_t LENH_CHAY_THUAN = 0x0012; // bit 0-1 = 10: chạy, bit 4-5 = 01: thuận
const uint16_t LENH_CHAY_NGHICH = 0x0022; // bit 4-5 = 10: nghịch
const uint16_t GIA_TRI_XOA_LOI = 0x0002; // 2002H bit 1 = 1: reset lỗi
const float HE_SO_TAN_SO = 0.01, HE_SO_DONG = 0.1; // 2001H, 2103H tính bằng 0,01 Hz; 2104H bằng 0,1 A
const uint32_t CHO_TRA_LOI_MS = 200, KHOANG_LANG_MS = 5, CHU_KY_DOC_MS = 500;
uint8_t khung[64];
int loiLienTiep = 0;
uint32_t lucDoc = 0;
String dongLenh;
uint16_t tinhCrc16(const uint8_t *duLieu, int soByte) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < soByte; i++) {
crc ^= duLieu[i];
for (int bit = 0; bit < 8; bit++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
return crc;
}
// Thêm CRC, phát khung, nhận khung trả lời; trả về số byte nhận được đã đúng CRC và địa chỉ (0 nếu lỗi)
int hoiDap(int soByte) {
uint16_t crc = tinhCrc16(khung, soByte);
khung[soByte] = crc & 0xFF;
khung[soByte + 1] = crc >> 8;
while (Serial2.available()) Serial2.read();
delay(5); // khoảng lặng ≥ 3,5 ký tự giữa hai khung (khoảng 4 ms ở 9600 baud)
digitalWrite(DE_RE_PIN, HIGH);
Serial2.write(khung, soByte + 2);
Serial2.flush(); // chờ phát xong byte cuối rồi mới chuyển sang nhận
digitalWrite(DE_RE_PIN, LOW);
uint32_t batDau = millis();
while (!Serial2.available()) {
if (millis() - batDau > CHO_TRA_LOI_MS) { Serial.println("Bien tan khong tra loi"); return 0; }
delay(1);
}
int n = 0;
uint32_t lucNhan = millis();
while (millis() - lucNhan < KHOANG_LANG_MS) {
if (Serial2.available()) {
uint8_t b = Serial2.read();
if (n < (int)sizeof(khung)) khung[n++] = b;
lucNhan = millis();
}
}
if (n < 5 || tinhCrc16(khung, n - 2) != (khung[n - 2] | (khung[n - 1] << 8))) { Serial.println("Sai CRC"); return 0; }
if (khung[0] != DIA_CHI_BIEN_TAN) return 0;
if (khung[1] & 0x80) { Serial.printf("Bien tan bao loi Modbus, ma %u\n", (unsigned)khung[2]); return 0; }
return n;
}
// Mã hàm 06: ghi một thanh ghi; biến tần trả lại đúng khung đã gửi
bool ghiThanhGhi(uint16_t diaChi, uint16_t giaTri) {
khung[0] = DIA_CHI_BIEN_TAN; khung[1] = 0x06;
khung[2] = diaChi >> 8; khung[3] = diaChi & 0xFF;
khung[4] = giaTri >> 8; khung[5] = giaTri & 0xFF;
bool ok = hoiDap(6) == 8 && khung[1] == 0x06;
loiLienTiep = ok ? 0 : loiLienTiep + 1;
return ok;
}
// Mã hàm 03: đọc 'so' thanh ghi liên tiếp
bool docThanhGhi(uint16_t diaChi, uint16_t so, uint16_t *ketQua) {
khung[0] = DIA_CHI_BIEN_TAN; khung[1] = 0x03;
khung[2] = diaChi >> 8; khung[3] = diaChi & 0xFF;
khung[4] = so >> 8; khung[5] = so & 0xFF;
int n = hoiDap(6);
bool ok = n == 5 + 2 * so && khung[1] == 0x03 && khung[2] == 2 * so;
if (ok) for (int i = 0; i < so; i++) ketQua[i] = (khung[3 + 2 * i] << 8) | khung[4 + 2 * i];
loiLienTiep = ok ? 0 : loiLienTiep + 1;
return ok;
}
void xuLyLenh(String s) {
s.trim();
bool ok = false;
if (s.startsWith("f ")) {
float hz = constrain(s.substring(2).toFloat(), 0.0f, TAN_SO_MAX);
ok = ghiThanhGhi(TG_TAN_SO, (uint16_t)lroundf(hz / HE_SO_TAN_SO));
Serial.printf("Dat tan so %.2f Hz: %s\n", hz, ok ? "OK" : "LOI");
return;
}
if (s == "chay") ok = ghiThanhGhi(TG_LENH, LENH_CHAY_THUAN);
else if (s == "nghich") ok = ghiThanhGhi(TG_LENH, LENH_CHAY_NGHICH);
else if (s == "dung") ok = ghiThanhGhi(TG_LENH, LENH_DUNG);
else if (s == "reset") ok = ghiThanhGhi(TG_XOA_LOI, GIA_TRI_XOA_LOI);
else { Serial.println("Lenh: chay | nghich | dung | reset | f <Hz>"); return; }
Serial.printf("%s: %s\n", s.c_str(), ok ? "OK" : "LOI");
}
void setup() {
Serial.begin(115200);
pinMode(DE_RE_PIN, OUTPUT);
digitalWrite(DE_RE_PIN, LOW); // mặc định nghe
Serial2.begin(TOC_DO_BAUD, KHUNG_DU_LIEU, RX2_PIN, TX2_PIN);
Serial.println("Lenh: chay | nghich | dung | reset | f <Hz>");
}
void loop() {
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') { xuLyLenh(dongLenh); dongLenh = ""; }
else if (c != '\r') dongLenh += c;
}
if (millis() - lucDoc < CHU_KY_DOC_MS) return;
lucDoc = millis();
uint16_t tg[5]; // 2100H mã lỗi, 2101H trạng thái, 2102H tần số đặt, 2103H tần số ra, 2104H dòng ra
if (docThanhGhi(TG_TRANG_THAI, 5, tg)) {
uint8_t chay = tg[1] & 0x03;
Serial.printf("Dat %.2f Hz | Ra %.2f Hz | %.1f A | %s | Ma loi %u\n", tg[2] * HE_SO_TAN_SO, tg[3] * HE_SO_TAN_SO,
tg[4] * HE_SO_DONG, chay == 3 ? "Dang chay" : chay == 0 ? "Dung" : "Dang tang/giam toc", (unsigned)tg[0]);
} else if (loiLienTiep == 3) {
Serial.println("MAT KET NOI voi bien tan: kiem tra day A/B, dia chi, toc do baud, khung du lieu");
}
}
hoiDap()chờ khoảng lặng giữa hai khung, bật DE để phát,Serial2.flush()chờ phát xong byte cuối rồi mới tắt DE để nghe. Tắt DE sớm là mất byte CRC cuối, biến tần bỏ khung.- Khung trả lời được kiểm tra CRC, địa chỉ, và mã lỗi Modbus (mã hàm có bit 0x80, ví dụ địa chỉ thanh ghi không hợp lệ).
TAN_SO_MAXchặn không cho đặt tần số quá giới hạn động cơ; biến tần cũng nên đặt tần số lớn nhất tương ứng.- Muốn điều khiển từ điện thoại hay web, gọi
ghiThanhGhi()từ trang web, MQTT… thay cho lệnh gõ trong Serial.
6. Kết quả

7. Lỗi hay gặp
| Hiện tượng | Nguyên nhân | Cách xử lý |
|---|---|---|
| Biến tần không trả lời | Sai địa chỉ, baud, khung dữ liệu; đảo A/B; chưa tắt bật lại biến tần sau khi đổi tham số | Đối chiếu từng thông số; thử đảo A/B; tắt nguồn biến tần rồi bật lại |
| Đọc được nhưng lệnh chạy không có tác dụng | Nguồn lệnh, nguồn tần số chưa chuyển sang truyền thông | Đặt lại hai tham số nguồn lệnh và nguồn tần số |
| Sai CRC lúc có lúc không | Nhiễu từ dây động cơ, thiếu điện trở đầu cuối | Cáp xoắn có vỏ chống nhiễu, đi riêng; điện trở 120 Ω; module cách ly |
| Báo lỗi Modbus mã 2 | Địa chỉ thanh ghi không có trên mẫu biến tần này | Tra lại bảng địa chỉ của đúng mẫu |
| Tần số chạy gấp 10 lần hoặc 1/10 | Sai đơn vị (0,1 Hz hay 0,01 Hz) | Sửa HE_SO_TAN_SO theo sách |
| Biến tần báo lỗi truyền thông rồi dừng | Hết thời gian chờ vì ESP32 không gửi khung nào | Giữ chu kỳ đọc trạng thái đều đặn; tăng thời gian chờ nếu cần |
An toàn: biến tần nối điện lưới và tích điện trong tụ sau khi cắt nguồn. Đấu dây RS-485 khi đã cắt điện và chờ đèn báo trên biến tần tắt hẳn; thử lần đầu với động cơ không tải.
Xem thêm: Modbus RTU (phần 2): ESP32 làm thiết bị tớ; ESP32 làm cổng chuyển Modbus TCP sang RTU; PZEM-004T đo điện năng qua Modbus.
Bài cùng cụm chủ đề: biến tần là gì và cài tham số cơ bản; mạch khởi động từ đơn; mạch đảo chiều động cơ 3 pha.
Xem thêm