Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU

Hướng dẫn dùng ESP32 làm máy khách Modbus TCP đọc thanh ghi của một thiết bị trong mạng, và làm cổng chuyển đổi Modbus TCP sang Modbus RTU qua RS-485: cách đổi khung, mã thiết bị, thời gian chờ, mã lỗi 0x0B.

Phần 1 đã dùng ESP32 làm máy chủ Modbus TCP. Phần 2 làm hai việc còn lại: dùng ESP32 làm máy khách (client) để đọc một thiết bị Modbus TCP có sẵn, và dùng ESP32 làm cổng chuyển đổi (gateway) giữa Modbus TCP và Modbus RTU. Cổng chuyển đổi là thứ rất hay gặp trong thực tế: các thiết bị cũ chỉ có RS-485, trong khi phần mềm giám sát chỉ nói Modbus TCP qua mạng. Cả hai chương trình đều tự xử lý khung tin, không dùng thư viện.

Bài dùng lại kiến thức về phần đầu MBAP, khung Modbus RTU và module RS-485 của các bài trước trong loạt.

1. Máy khách Modbus TCP làm những gì?

Năm bước của máy khách Modbus TCP: kết nối tới cổng 502, gửi khung hỏi, đọc đủ bảy byte phần đầu MBAP, đọc phần PDU theo trường độ dài, kiểm tra số giao dịch và mã hàm; kèm khung hỏi 12 byte và khung trả lời 17 byte
Các bước của máy khách và cặp khung hỏi, trả lời trong bài.
  1. Kết nối TCP tới địa chỉ IP của thiết bị, cổng 502. Một kết nối dùng cho nhiều lần hỏi.
  2. Gửi khung hỏi: 7 byte MBAP rồi tới PDU. Mỗi lần hỏi tăng số giao dịch lên 1.
  3. Đọc đủ 7 byte phần đầu của khung trả lời, có giới hạn thời gian.
  4. Đọc phần còn lại: số byte bằng trường độ dài trừ 1.
  5. Kiểm tra: số giao dịch phải trùng với khung hỏi; mã hàm đúng hoặc là mã báo lỗi; số byte dữ liệu khớp với số thanh ghi đã hỏi.

Vì TCP là dòng byte, khung trả lời có thể tới thành nhiều mẩu. Chương trình phải đọc cho đủ số byte cần, không được coi một lần đọc là một khung.

2. Chuẩn bị

  • Một mạch ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) cho mỗi vai trò, và mạng Wi-Fi 2,4 GHz.
  • Cho chương trình 1: một máy chủ Modbus TCP để đọc, ví dụ mạch ESP32 chạy chương trình của phần 1.
  • Cho chương trình 2: một module chuyển đổi RS-485 và ít nhất một thiết bị Modbus RTU, ví dụ mạch ESP32 chạy chương trình thiết bị tớ của bài Modbus RTU.
  • Breadboard và dây cắm.
  • Arduino IDE 2.x với gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Không cần cài thêm thư viện.

3. Chương trình 1: ESP32 làm máy khách Modbus TCP

Sửa DIA_CHI_MAY_CHU thành địa chỉ IP của thiết bị cần đọc. Mỗi 2 giây chương trình đọc 4 thanh ghi giữ từ địa chỉ 0 và in cả khung gửi, khung nhận.

/*
  PYWORLD.VN - Modbus TCP với ESP32 làm máy khách (client): kết nối tới một thiết bị Modbus TCP và đọc thanh ghi giữ
  Mỗi 2 giây gửi một khung đọc 4 thanh ghi (mã hàm 0x03), in khung gửi, khung nhận và giá trị đọc được
  Sửa DIA_CHI_MAY_CHU cho đúng địa chỉ IP của thiết bị cần đọc; cổng mặc định của Modbus TCP là 502
*/

#include <WiFi.h>

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

const IPAddress DIA_CHI_MAY_CHU(192, 168, 1, 50);
const uint16_t CONG_MODBUS = 502;
const uint8_t MA_THIET_BI = 1;
const unsigned long THOI_GIAN_CHO_MS = 1000;

WiFiClient ketNoi;
uint16_t soGiaoDich = 0;

uint16_t lay16(const uint8_t *viTri) {
  return (viTri[0] << 8) | viTri[1];
}

void dat16(uint8_t *viTri, uint16_t giaTri) {
  viTri[0] = giaTri >> 8;
  viTri[1] = giaTri & 0xFF;
}

void inKhung(const char *nhan, const uint8_t *dau, int soByteDau, const uint8_t *sau, int soByteSau) {
  Serial.print(nhan);
  for (int i = 0; i < soByteDau; i++) {
    Serial.printf(" %02X", dau[i]);
  }
  for (int i = 0; i < soByteSau; i++) {
    Serial.printf(" %02X", sau[i]);
  }
  Serial.println();
}

// TCP là dòng byte: phải đọc cho đủ đúng số byte cần, có giới hạn thời gian
bool docDuByte(uint8_t *dich, int soByte) {
  int daDoc = 0;
  unsigned long batDau = millis();
  while (daDoc < soByte) {
    if (ketNoi.available()) {
      dich[daDoc++] = ketNoi.read();
    } else if (!ketNoi.connected() || millis() - batDau > THOI_GIAN_CHO_MS) {
      return false;
    } else {
      delay(1);
    }
  }
  return true;
}

bool docThanhGhiGiu(uint16_t thanhGhiDau, uint16_t soLuong, uint16_t *ketQua) {
  // Phần đầu MBAP 7 byte, rồi PDU 5 byte: mã hàm, thanh ghi đầu, số lượng
  uint8_t khung[12];
  soGiaoDich++;
  dat16(&khung[0], soGiaoDich);   // Số giao dịch: máy chủ gửi lại đúng số này trong khung trả lời
  dat16(&khung[2], 0);            // Mã giao thức: luôn là 0 với Modbus
  dat16(&khung[4], 6);            // Độ dài phần còn lại: mã thiết bị (1) + PDU (5)
  khung[6] = MA_THIET_BI;
  khung[7] = 0x03;
  dat16(&khung[8], thanhGhiDau);
  dat16(&khung[10], soLuong);

  while (ketNoi.available()) {
    ketNoi.read();                // Bỏ dữ liệu cũ còn sót
  }
  ketNoi.write(khung, sizeof(khung));
  inKhung("Gui :", khung, sizeof(khung), NULL, 0);

  uint8_t dau[7];
  if (!docDuByte(dau, 7)) {
    Serial.println("  Khong co tra loi: dong ket noi de ket noi lai");
    ketNoi.stop();
    return false;
  }
  uint16_t doDai = lay16(&dau[4]);
  if (lay16(&dau[0]) != soGiaoDich || lay16(&dau[2]) != 0 || doDai < 3 || doDai > 254) {
    Serial.println("  Phan dau tra loi khong hop le: dong ket noi");
    ketNoi.stop();
    return false;
  }
  uint8_t pdu[254];
  if (!docDuByte(pdu, doDai - 1)) {
    Serial.println("  Khung tra loi bi thieu: dong ket noi");
    ketNoi.stop();
    return false;
  }
  inKhung("Nhan:", dau, 7, pdu, doDai - 1);

  if (pdu[0] == (0x03 | 0x80)) {
    Serial.printf("  Thiet bi bao loi 0x%02X\n", pdu[1]);
    return false;
  }
  if (pdu[0] != 0x03 || pdu[1] != soLuong * 2 || doDai - 1 != 2 + soLuong * 2) {
    Serial.println("  Noi dung tra loi khong khop voi cau hoi");
    return false;
  }
  for (uint16_t i = 0; i < soLuong; i++) {
    ketQua[i] = lay16(&pdu[2 + i * 2]);
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.printf("\nDang ket noi WiFi %s", WIFI_SSID);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nIP cua mach nay: %s\n", WiFi.localIP().toString().c_str());
}

void loop() {
  if (!ketNoi.connected()) {
    Serial.printf("Dang ket noi toi %s cong %u...\n", DIA_CHI_MAY_CHU.toString().c_str(), CONG_MODBUS);
    if (!ketNoi.connect(DIA_CHI_MAY_CHU, CONG_MODBUS, 3000)) {
      Serial.println("  Khong ket noi duoc, thu lai sau 3 giay");
      delay(3000);
      return;
    }
    Serial.println("  Da ket noi");
  }

  uint16_t giaTri[4];
  if (docThanhGhiGiu(0, 4, giaTri)) {
    Serial.printf("  Nhiet do %.1f C | do am %.1f %% | thoi gian chay %u s | diem dat %.1f C\n",
                  (int16_t)giaTri[0] / 10.0, giaTri[1] / 10.0, giaTri[2], (int16_t)giaTri[3] / 10.0);
  }
  delay(2000);
}
  • Hàm docDuByte() đọc đúng số byte yêu cầu hoặc trả về thất bại khi quá 1 giây.
  • Khi không có trả lời hoặc phần đầu không hợp lệ, chương trình đóng kết nối. Lần lặp sau nó kết nối lại từ đầu; đây là cách chắc chắn nhất để hai bên đồng bộ lại.
  • Để đọc một thiết bị khác, sửa địa chỉ IP, mã thiết bị, địa chỉ thanh ghi và cách đổi giá trị theo bảng thanh ghi của thiết bị đó.

4. Cổng chuyển đổi Modbus TCP sang RTU

Sơ đồ cổng chuyển đổi Modbus: máy khách nói Modbus TCP qua Wi-Fi với ESP32, ESP32 nói Modbus RTU qua RS-485 với ba thiết bị địa chỉ 1, 2, 3; mã thiết bị trong khung TCP chọn thiết bị trên RS-485
ESP32 đứng giữa mạng Wi-Fi và đường dây RS-485.
  • Phía mạng, cổng chuyển đổi là một máy chủ Modbus TCP ở cổng 502.
  • Phía RS-485, nó là thiết bị chủ Modbus RTU.
  • Mã thiết bị trong phần đầu MBAP trở thành địa chỉ thiết bị tớ trên RS-485. Nhờ đó một cổng chuyển đổi phục vụ được nhiều thiết bị trên cùng đường dây.
  • Cổng chuyển đổi không cần hiểu mã hàm hay thanh ghi: nó chỉ đổi phần bao quanh khung.
Bốn bước chuyển đổi khung qua cổng chuyển đổi Modbus: khung hỏi Modbus TCP, khung hỏi Modbus RTU có thêm CRC, khung trả lời RTU của thiết bị, và khung trả lời Modbus TCP với số giao dịch cũ và độ dài mới
Một lần hỏi qua cổng chuyển đổi: PDU đi nguyên vẹn, chỉ phần bao quanh thay đổi.
BướcCổng chuyển đổi làm gì
Nhận khung TCPChờ đủ khung theo trường độ dài; ghi nhớ số giao dịch
Gửi khung RTULấy mã thiết bị làm địa chỉ, giữ nguyên PDU, thêm CRC-16, phát lên RS-485
Chờ trả lờiTối đa 500 ms; tách khung bằng khoảng lặng; kiểm tra CRC và địa chỉ
Trả khung TCPBỏ địa chỉ và CRC, gắn lại MBAP với số giao dịch cũ và độ dài mới
Không có trả lời hợp lệTự trả lời bằng mã lỗi 0x0B: thiết bị đích không trả lời

5. Chương trình 2: ESP32 làm cổng chuyển đổi

Module RS-485 nối như trong bài RS-485: DI vào TX2, RO vào RX2 (qua mạch chia áp nếu module chạy 5 V), DE và RE nối chung vào D4. Đường RS-485 chạy 9600 baud, 8N1.

/*
  PYWORLD.VN - Cổng chuyển đổi Modbus TCP sang Modbus RTU bằng ESP32: hỏi qua Wi-Fi, ESP32 hỏi lại thiết bị trên RS-485
  Nhận khung Modbus TCP ở cổng 502, đổi thành khung RTU (mã thiết bị thành địa chỉ, thêm CRC), gửi qua RS-485 rồi trả kết quả về
  Module RS-485: DI = TX2 (GPIO17), RO = RX2 (GPIO16), DE và RE = D4; đường RS-485 chạy 9600 baud, 8N1
*/

#include <WiFi.h>

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

#define RX2_PIN 16
#define TX2_PIN 17
#define DE_RE_PIN 4

const uint16_t CONG_MODBUS = 502;
const uint32_t TOC_DO_BAUD = 9600;
const unsigned long THOI_GIAN_CHO_RTU_MS = 500;
const unsigned long KHOANG_LANG_MS = 5;
const unsigned long THOI_GIAN_IM_LANG_MS = 60000;
const uint8_t LOI_DICH_KHONG_TRA_LOI = 0x0B;   // Mã lỗi của cổng chuyển đổi: thiết bị đích không trả lời

WiFiServer mayChu(CONG_MODBUS);
WiFiClient mayKhach;
uint8_t boDem[300];
int soByte = 0;
unsigned long lucNhanCuoi = 0;

uint16_t lay16(const uint8_t *viTri) {
  return (viTri[0] << 8) | viTri[1];
}

uint16_t tinhCrc16(const uint8_t *duLieu, size_t soByteDuLieu) {
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < soByteDuLieu; i++) {
    crc ^= duLieu[i];
    for (int k = 0; k < 8; k++) {
      crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
  }
  return crc;
}

// Gửi một khung RTU (thêm CRC) rồi chờ khung trả lời; trả về số byte nhận được, 0 nếu quá thời gian
int hoiQuaRs485(uint8_t *khung, int doDai, int toiDa) {
  uint16_t crc = tinhCrc16(khung, doDai);
  khung[doDai] = crc & 0xFF;
  khung[doDai + 1] = crc >> 8;

  while (Serial2.available()) {
    Serial2.read();
  }
  delay(KHOANG_LANG_MS);
  digitalWrite(DE_RE_PIN, HIGH);
  Serial2.write(khung, doDai + 2);
  Serial2.flush();
  digitalWrite(DE_RE_PIN, LOW);

  unsigned long batDau = millis();
  while (!Serial2.available()) {
    if (millis() - batDau > THOI_GIAN_CHO_RTU_MS) {
      return 0;
    }
    delay(1);
  }
  int daNhan = 0;
  unsigned long lucByteCuoi = millis();
  while (millis() - lucByteCuoi < KHOANG_LANG_MS) {
    if (Serial2.available()) {
      uint8_t giaTri = Serial2.read();
      if (daNhan < toiDa) {
        khung[daNhan++] = giaTri;
      }
      lucByteCuoi = millis();
    }
  }
  return daNhan;
}

void xuLyBoDem() {
  while (soByte >= 7) {
    uint16_t maGiaoThuc = lay16(&boDem[2]);
    uint16_t doDai = lay16(&boDem[4]);   // Mã thiết bị + PDU
    if (maGiaoThuc != 0 || doDai < 2 || doDai > 254) {
      Serial.println("Phan dau MBAP khong hop le: dong ket noi");
      mayKhach.stop();
      soByte = 0;
      return;
    }
    int doDaiKhung = 6 + doDai;
    if (soByte < doDaiKhung) {
      return;
    }

    // Khung RTU = mã thiết bị + PDU + CRC. Bảy byte MBAP chỉ dùng ở phía TCP
    uint8_t diaChi = boDem[6];
    uint8_t maHam = boDem[7];
    uint8_t rtu[260];
    memcpy(rtu, &boDem[6], doDai);
    int daNhan = hoiQuaRs485(rtu, doDai, sizeof(rtu));

    uint8_t traLoi[270];
    memcpy(traLoi, boDem, 4);            // Số giao dịch và mã giao thức giữ nguyên
    traLoi[6] = diaChi;
    int doDaiPdu;
    bool hopLe = daNhan >= 5 && rtu[0] == diaChi
                 && tinhCrc16(rtu, daNhan - 2) == (uint16_t)(rtu[daNhan - 2] | (rtu[daNhan - 1] << 8));
    if (hopLe) {
      doDaiPdu = daNhan - 3;             // Bỏ byte địa chỉ và hai byte CRC
      memcpy(&traLoi[7], &rtu[1], doDaiPdu);
      Serial.printf("Dia chi %u, ma ham 0x%02X: thiet bi tra loi %d byte\n", diaChi, maHam, daNhan);
    } else {
      traLoi[7] = maHam | 0x80;
      traLoi[8] = LOI_DICH_KHONG_TRA_LOI;
      doDaiPdu = 2;
      Serial.printf("Dia chi %u, ma ham 0x%02X: khong co tra loi hop le, bao loi 0x0B\n", diaChi, maHam);
    }
    traLoi[4] = (doDaiPdu + 1) >> 8;
    traLoi[5] = (doDaiPdu + 1) & 0xFF;
    mayKhach.write(traLoi, 7 + doDaiPdu);

    soByte -= doDaiKhung;
    memmove(boDem, boDem + doDaiKhung, soByte);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(DE_RE_PIN, OUTPUT);
  digitalWrite(DE_RE_PIN, LOW);
  Serial2.begin(TOC_DO_BAUD, SERIAL_8N1, RX2_PIN, TX2_PIN);

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.printf("\nDang ket noi WiFi %s", WIFI_SSID);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  mayChu.begin();
  Serial.printf("\nCong chuyen doi Modbus TCP -> RTU: %s cong %u, RS-485 %u baud\n",
                WiFi.localIP().toString().c_str(), CONG_MODBUS, (unsigned)TOC_DO_BAUD);
}

void loop() {
  if (!mayKhach || !mayKhach.connected()) {
    WiFiClient moi = mayChu.accept();
    if (moi) {
      mayKhach = moi;
      soByte = 0;
      lucNhanCuoi = millis();
      Serial.printf("May khach %s da ket noi\n", mayKhach.remoteIP().toString().c_str());
    }
    return;
  }

  while (mayKhach.available() && soByte < (int)sizeof(boDem)) {
    boDem[soByte++] = mayKhach.read();
    lucNhanCuoi = millis();
  }
  xuLyBoDem();

  if (millis() - lucNhanCuoi > THOI_GIAN_IM_LANG_MS) {
    Serial.println("May khach im lang qua lau: dong ket noi");
    mayKhach.stop();
  }
}
  • Trong lúc chờ thiết bị RS-485 trả lời (tối đa 500 ms), cổng chuyển đổi không nhận khung TCP mới. Với Modbus điều này là đúng: trên RS-485 mỗi lúc chỉ có một câu hỏi.
  • Thời gian chờ của máy khách phía TCP phải dài hơn thời gian chờ phía RS-485, nếu không máy khách sẽ bỏ cuộc trước khi cổng chuyển đổi kịp báo lỗi.
  • Khung trả lời báo lỗi của chính thiết bị RS-485 (ví dụ mã lỗi 02) được chuyển nguyên vẹn về máy khách.

6. Kết quả mong đợi

Hình minh họa Serial Monitor của ESP32 làm máy khách Modbus TCP và làm cổng chuyển đổi: khung gửi và khung nhận kèm giá trị bốn thanh ghi; nhật ký cổng chuyển đổi với thiết bị địa chỉ 1 trả lời 13 byte và địa chỉ 5 báo lỗi 0x0B
Kết quả mong đợi trên Serial Monitor của hai chương trình (hình minh họa).
  1. Chương trình 1: mở Serial Monitor ở 115200 baud. Sau khi kết nối, mỗi 2 giây hiện một cặp khung và bốn giá trị. Tắt máy chủ: chương trình báo không kết nối được và tự thử lại.
  2. Chương trình 2: dùng một máy khách Modbus TCP (phần mềm trên máy tính hoặc chương trình 1) kết nối tới địa chỉ IP của cổng chuyển đổi, mã thiết bị 1. Dữ liệu đọc được là của thiết bị tớ trên RS-485.
  3. Hỏi một mã thiết bị không tồn tại, ví dụ 5: sau khoảng nửa giây máy khách nhận về mã lỗi 0x0B.

7. Lưu ý khi dùng thật

  • Bảo mật: Modbus không có xác thực. Ai kết nối được tới cổng chuyển đổi cũng ghi được vào mọi thiết bị trên RS-485. Chỉ dùng trong mạng nội bộ tin cậy.
  • Nhiều máy khách: chương trình phục vụ một máy khách. Cổng chuyển đổi thương mại xếp hàng các câu hỏi từ nhiều máy khách vì đường RS-485 chỉ xử lý được từng câu một.
  • Mất Wi-Fi: nên thêm phần tự kết nối lại theo bài kết nối Wi-Fi trên ESP32, và đặt IP tĩnh để địa chỉ không đổi.
  • Độ tin cậy: trong môi trường nhiều nhiễu sóng, Ethernet có dây ổn định hơn Wi-Fi; phần Modbus của chương trình không thay đổi.

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

Hiện tượngNguyên nhân thường gặpCách xử lý
Máy khách báo không kết nối đượcSai địa chỉ IP, thiết bị chưa bật, hoặc thiết bị đang bận với máy khách khácKiểm tra IP và mạng; ngắt máy khách khác
Phần đầu trả lời không hợp lệSố giao dịch lệch do còn sót khung cũ, hoặc thiết bị không nói Modbus TCPChương trình tự đóng và kết nối lại; kiểm tra thiết bị đúng là Modbus TCP
Qua cổng chuyển đổi luôn nhận lỗi 0x0BSai mã thiết bị, sai tốc độ baud hoặc định dạng khung phía RS-485, nối ngược A với BMã thiết bị phải bằng địa chỉ thiết bị tớ; đối chiếu thông số RS-485; đổi chéo hai dây
Máy khách hết thời gian chờ trước khi có trả lờiThời gian chờ của máy khách ngắn hơn của cổng chuyển đổiĐặt thời gian chờ của máy khách từ 1 giây trở lên
Thỉnh thoảng nhận lỗi 0x0BNhiễu trên RS-485 làm sai CRC, hoặc thiết bị trả lời chậmKiểm tra đường dây và điện trở đầu cuối; tăng THOI_GIAN_CHO_RTU_MS

9. Xem thêm


Tin tức liên quan

Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32
Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32

Giải thích Bluetooth Low Energy: quảng bá và kết nối, cấu trúc GATT gồm dịch vụ, đặc tính, mô tả, UUID. Hướng dẫn cho ESP32 làm thiết bị BLE có đặc tính đọc, ghi, thông báo và thử bằng điện thoại.

Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32
Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32

Giải thích điều khiển hồng ngoại: sóng mang 38 kHz, mắt thu và giao thức NEC với khung 32 bit, mã lặp. Hướng dẫn tự giải mã phím điều khiển và tự phát lệnh bằng ESP32, không dùng thư viện ngoài.

RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32
RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32

Giải thích sóng RF 433 MHz và điều chế ASK/OOK, cách mã hóa bit bằng độ rộng xung và khung tin 24 bit. Hướng dẫn tự viết chương trình phát và thu trên hai mạch ESP32, không dùng thư viện ngoài.

Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32
Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32

Giải thích vòng dòng 4-20 mA: vì sao dùng dòng điện, vì sao bắt đầu từ 4 mA, cách đổi ra giá trị đo và ngưỡng báo lỗi. Hướng dẫn đọc bằng ESP32 qua điện trở shunt 120 Ω, với ADC có sẵn và với ADS1115.

CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32
CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32

Phần 2 về CAN bus với ESP32: đặt bộ lọc nhận theo mã lọc và mặt nạ, chọn tốc độ bit theo chiều dài dây, hiểu ba trạng thái lỗi của nút và viết chương trình theo dõi bộ đếm lỗi, tự hồi phục sau bus-off.

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.

Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi
Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi

Giải thích Modbus TCP/IP: phần đầu MBAP, cổng 502, điểm khác với Modbus RTU. Kèm chương trình ESP32 làm máy chủ Modbus TCP qua Wi-Fi với 10 thanh ghi giữ và 8 cuộn, tự phân tích khung, không dùng thư viện.

TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32
TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32

Giải thích TCP và UDP cho người làm vi điều khiển: địa chỉ IP, cổng, kết nối, xác nhận, vì sao TCP là dòng byte. Kèm hai chương trình ESP32: máy chủ TCP nhận lệnh và phát, nhận gói UDP trong mạng Wi-Fi.


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