Đ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

Sơ đồ khối ESP32 nối module MAX485, cặp dây xoắn A B có điện trở 120 ohm hai đầu tới cọc SG+ SG- của biến tần, biến tần chạy động cơ 3 pha; ghi chú A nối SG+, B nối SG-, dây tín hiệu đi riêng dây động lực
Một cặp dây thay cho các dây điều khiển.
Module MAX485Nối tớiGhi chú
DIGPIO17 (TX2)ESP32 phát
ROGPIO16 (RX2)ESP32 nhận
DE, RE (nối chung)GPIO4mức cao: phát; mức thấp: nghe
VCC, GND3V3 (hoặc 5V tùy module), GNDmodule 5 V thì RO cần chia áp xuống 3,3 V
A, BSG+ (D+), SG− (D−) của biến tầncọ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

Bảng tham số cần đặt để điều khiển biến tần qua Modbus: nguồn lệnh chạy và nguồn tần số là truyền thông RS-485, địa chỉ 1, tốc độ 9600, giao thức RTU 8N2 hoặc 8E1, xử lý khi mất truyền thông; ví dụ Delta VFD-E tham số 02.00, 02.01 và nhóm 09
Bốn thông số truyền thông phải trùng hai bên: địa chỉ, baud, chế độ RTU, khung dữ liệu.

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

Bảng thanh ghi lệnh 2000H của biến tần Delta: bit 0-1 bằng 01 là dừng, 10 là chạy; bit 4-5 bằng 01 chiều thuận, 10 chiều nghịch; giá trị 0x0012 chạy thuận, 0x0022 chạy nghịch, 0x0001 dừng, 2002H bit 1 xóa lỗi
Chạy thuận = bit chạy (0x02) cộng bit thuận (0x10) = 0x0012.

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

Khung Modbus RTU từng byte: 01 06 20 00 00 12 02 07 chạy thuận, 01 06 20 01 09 C4 D4 09 đặt 25 Hz, 01 03 21 00 00 05 8F F5 đọc 5 thanh ghi trạng thái, và khung trả lời 10 byte dữ liệu gồm mã lỗi, trạng thái, tần số đặt, tần số ra 24,87 Hz, dòng 1,3 A
Mã hàm 06 ghi một thanh ghi, mã hàm 03 đọc nhiều thanh ghi liên tiếp.

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_MAX chặ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ả

Serial Monitor khi điều khiển biến tần: đặt tần số 25 Hz, lệnh chạy, tần số ra tăng dần 6,4 Hz, 18,8 Hz rồi 24,87 Hz đang chạy, lệnh dừng, tần số ra giảm về 0
Tần số ra tăng, giảm theo thời gian tăng tốc, giảm tốc cài trên biến tần.

7. Lỗi hay gặp

Hiện tượngNguyên nhânCách xử lý
Biến tần không trả lờiSai đị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ụngNguồ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ôngNhiễu từ dây động cơ, thiếu điện trở đầu cuốiCá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àyTra lại bảng địa chỉ của đúng mẫu
Tần số chạy gấp 10 lần hoặc 1/10Sai đơ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ừngHết thời gian chờ vì ESP32 không gửi khung nàoGiữ 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.


Tin tức liên quan

Vali nhựa hay vali nhôm: chọn loại nào để đựng thiết bị, dụng cụ
Vali nhựa hay vali nhôm: chọn loại nào để đựng thiết bị, dụng cụ

So sánh vali nhựa và vali nhôm đựng thiết bị theo chống nước, chống bụi, chịu va đập, nhiệt độ, vẻ ngoài, khóa và bảo dưỡng; khi nào nên chọn mỗi loại và sơ đồ chọn nhanh.

Chấm điểm dự án STEM: bảng tiêu chí 4 mức có trọng số, đánh giá chéo, gắn với Thông tư 22/2021
Chấm điểm dự án STEM: bảng tiêu chí 4 mức có trọng số, đánh giá chéo, gắn với Thông tư 22/2021

Hướng dẫn giáo viên chấm dự án STEM: ba nguyên tắc, bảng tiêu chí 4 mức cho 8 tiêu chí có trọng số, ví dụ tính điểm, phiếu đánh giá chéo, đưa điểm dự án vào đánh giá theo Thông tư 22.

ADC 24 bit HX711, ADS1232, ADS1220, ADS1256: so sánh nhiễu, tốc độ và chọn loại nào
ADC 24 bit HX711, ADS1232, ADS1220, ADS1256: so sánh nhiễu, tốc độ và chọn loại nào

So sánh ADC 24 bit HX711, ADS1232, ADS1220, ADS1256 theo nhiễu datasheet, gain, tốc độ, giao tiếp; tính độ phân giải trên load cell, chọn theo ứng dụng, code ESP32 đo nhiễu HX711.

Cài ESP-IDF trên Windows và dự án đầu tiên: idf.py, app_main, menuconfig, nạp và xem nhật ký
Cài ESP-IDF trên Windows và dự án đầu tiên: idf.py, app_main, menuconfig, nạp và xem nhật ký

Cài ESP-IDF trên Windows bằng bộ cài hoặc tiện ích VS Code, cấu trúc dự án, các lệnh idf.py, dự án đầu tiên với app_main và ESP_LOGI, tệp nạp và địa chỉ, so với Arduino.

CAN bus (phần 1): tín hiệu, khung tin, phân xử ưu tiên và gửi nhận bằng ESP32
CAN bus (phần 1): tín hiệu, khung tin, phân xử ưu tiên và gửi nhận bằng ESP32

Giải thích CAN bus: hai dây CAN_H, CAN_L, mức trội và mức lặn, khung tin có ID 11 bit, phân xử ưu tiên, CRC và ACK. Kèm hai chương trình ESP32 dùng bộ điều khiển TWAI: tự kiểm tra và hai nút gửi nhận.

Áp kế píttông: nguyên lý, tính áp suất chuẩn, các hiệu chính và cách dùng để hiệu chuẩn áp kế
Áp kế píttông: nguyên lý, tính áp suất chuẩn, các hiệu chính và cách dùng để hiệu chuẩn áp kế

Áp kế píttông tạo áp suất chuẩn từ khối lượng và diện tích: công thức đầy đủ, ví dụ tính, độ lớn các hiệu chính trọng trường, chênh độ cao, nhiệt độ, cân chéo và trình tự hiệu chuẩn áp kế.

Đầu nối JST XH2.54, PH2.0, SM và dây RCY: phân biệt bằng bước chân, chọn loại, nối dây và cực tính
Đầu nối JST XH2.54, PH2.0, SM và dây RCY: phân biệt bằng bước chân, chọn loại, nối dây và cực tính

Phân biệt đầu nối JST XH 2,5 mm, PH 2,0 mm, ZH, SH, SM và RCY bằng bước chân; dòng mỗi chân, cấu tạo, chân số 1, nối dây không cần kìm bấm và đo cực tính trước khi cắm pin.

An toàn khi dùng máy khắc laser và máy CNC: cấp laser, kính bảo hộ, khói độc, cháy và mũi phay
An toàn khi dùng máy khắc laser và máy CNC: cấp laser, kính bảo hộ, khói độc, cháy và mũi phay

Những điều phải biết trước khi bật máy khắc laser và máy CNC: cấp laser, chọn kính bảo hộ theo bước sóng và OD, vật liệu cấm khắc, phòng cháy, hút khói, an toàn dao phay và điện.


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