Industrial IoT với ESP32: Modbus và điều khiển real-time
Hướng dẫn thực tế về Modbus TCP, Modbus RTU qua RS-485 và cách kết nối ESP32 với PLC cho các prototype Industrial IoT đáng tin cậy hơn.
Chia sẻ

Nếu từng làm với PLC, bộ điều khiển nhiệt độ hoặc biến tần, có thể bạn đã gặp Modbus. Đây không phải giao thức hào nhoáng, nhưng lại là một trong những chiếc cầu hữu ích nhất để nối một controller nhỏ với thiết bị công nghiệp.
Trong bài này, mình dùng ESP32 như một IIoT gateway nhỏ: đọc giá trị từ PLC, ghi lại một số lệnh được cho phép, đồng thời tách đường điều khiển real-time khỏi phần Wi-Fi hay cloud. Ví dụ cố tình giữ vừa đủ nhỏ để bạn có thể mang vào một prototype thật mà không phải kéo theo quá nhiều abstraction.
ESP32 đứng ở đâu trong hệ thống IIoT?
ESP32 không phải thiết bị thay thế cho safety PLC. Mình thường xem nó như một edge controller linh hoạt cho việc giám sát, chuyển đổi giao thức, HMI cục bộ, chẩn đoán hoặc các lệnh không liên quan đến an toàn. Một ranh giới điển hình sẽ là:
cảm biến / actuator <-> PLC <-> Modbus <-> ESP32 <-> MQTT / dashboard
PLC vẫn nên chịu trách nhiệm cho logic máy và interlock an toàn. ESP32 có thể lấy dữ liệu, hiển thị dashboard, lưu đệm hoặc yêu cầu một chế độ vận hành đã được cho phép. Nhờ vậy, việc mất mạng không biến thành lỗi điều khiển máy.
Modbus TCP và Modbus RTU khác nhau ở đâu?
Cả hai cùng dùng mô hình ứng dụng: client gửi request, server trả response hoặc exception. Điểm khác thực tế nằm ở lớp truyền tải:
| Kiểu | Lớp vật lý / mạng | Mình thường chú ý |
|---|---|---|
| Modbus TCP | Ethernet hoặc Wi-Fi, thường dùng TCP port 502 | IP, Unit ID, reconnect, network timeout |
| Modbus RTU | Serial, phổ biến nhất là RS-485 | A/B, termination, baud/parity, khoảng lặng |
RTU có CRC trên đường serial. TCP dùng lớp TCP/IP nên không mang CRC của RTU. Điều đó không có nghĩa TCP tự nhiên đã an toàn: bản thân Modbus không có authentication hay authorization. Nếu có quyền ghi, hãy tách mạng và giới hạn ai được truy cập.

Hãy kiểm tra bus vật lý trước khi đi tìm lỗi trong map thanh ghi.
Register map là một bản hợp đồng
Phần khó nhất nhiều khi không phải code ESP32 mà là thống nhất ý nghĩa của từng thanh ghi. Trước khi viết driver, mình thường xin một bảng kiểu này từ tài liệu PLC:
| Ý nghĩa | Vùng Modbus | Địa chỉ trên tài liệu | Kiểu | Scale |
|---|---|---|---|---|
| Nhiệt độ bồn | Holding register | 40011 | uint16 | value / 10 |
| Lệnh bơm | Coil | 00001 | bool | 0 hoặc 1 |
| Cờ cảnh báo | Input register | 30021 | uint16 | bit field |
Địa chỉ như 40011 là quy ước hiển thị thường gặp trong manual. Trong request PDU, offset có thể là 10 vì dùng địa chỉ zero-based. Đừng đoán. Hãy đọc một giá trị đã biết, đối chiếu với màn hình PLC, rồi ghi rõ thiết bị đang dùng địa chỉ zero-based hay one-based.
Với dữ liệu lớn hơn 16 bit, cần xác nhận luôn thứ tự word và byte. Một giá trị float có thể được xếp thành hai register theo nhiều kiểu. Link Modbus vẫn khỏe nhưng số đọc ra sai hoàn toàn nếu nhầm endianness.
Modbus RTU với ESP32 và RS-485
UART của ESP32 không phải transceiver RS-485. Bạn vẫn cần transceiver phù hợp, đồng thời phải cân nhắc điện áp, cách ly và môi trường của máy. Trên bus, mình sẽ kiểm tra các điểm sau:
- Nối A với A, B với B theo đúng datasheet transceiver; tên A/B không phải lúc nào cũng thống nhất giữa các module.
- Dùng twisted pair và giữ nhánh rẽ ngắn.
- Chỉ đặt termination ở hai đầu vật lý của bus, không đặt ở mọi node.
- Xác định rõ biasing và chỉ triển khai một cách có chủ đích trên segment.
- Nếu môi trường yêu cầu, dùng cách ly hoặc xử lý reference phù hợp.
- Bắt đầu bằng đúng baud rate, parity, stop bit và slave ID trong manual PLC.
ESP-IDF có component ESP-Modbus chính thức, kèm ví dụ master/slave cho serial và TCP. API đã thay đổi giữa các major version, nên hãy pin version trong project và đọc đúng example cùng version trước khi copy một đoạn code cũ trên mạng.
Modbus TCP: dây dễ hơn nhưng kỷ luật không đổi
TCP loại bỏ nhiều lỗi dây RS-485, nhưng không loại bỏ lỗi giao thức. Hãy cho PLC một địa chỉ ổn định, đặt timeout kết nối và timeout response hữu hạn, đồng thời quyết định rõ logic reconnect. TCP master vẫn cần Unit ID đúng khi đi qua gateway hoặc thiết bị có sử dụng Unit ID.
Trình tự test đầu tiên của mình thường là:
- Kiểm tra đường mạng.
- Đọc một holding register đã có tài liệu.
- Đối chiếu với giá trị trên PLC.
- Đọc một block thanh ghi liền nhau.
- Sau đó mới thử ghi vào một register test vô hại.

Gateway đóng hộp giúp loại bỏ dây prototype khỏi tủ điện và cho mỗi sợi cáp một điểm đấu nối rõ ràng.
Tách timing điều khiển khỏi network
Đây là phần làm prototype trở nên đáng tin cậy hơn. Đừng để việc Wi-Fi reconnect hay một request Modbus chậm trực tiếp nắm quyền điều khiển relay.
Một cách chia đơn giản:
control task (chu kỳ cố định) -> nhận setpoint hợp lệ -> điều khiển output
modbus task -> đọc/ghi register -> phát snapshot
network task -> MQTT/HTTP, log, OTA, chẩn đoán
Control task cần có chu kỳ rõ ràng, lượng việc có giới hạn và chính sách khi dữ liệu bị stale. Nếu Modbus task chưa gửi dữ liệu mới trong thời gian cho phép, control task nên chuyển về trạng thái fallback đã biết, thay vì âm thầm dùng dữ liệu cũ.
Với FreeRTOS, bạn có thể dùng queue hoặc struct nhỏ để truyền snapshot giữa các task. Nếu dùng shared state, hãy bảo vệ bằng mutex; tốt hơn nữa là truyền ownership của một snapshot hoàn chỉnh. Tránh giữ lock trong lúc chờ network. Chỉ một quy tắc này đã loại được khá nhiều lỗi timing chập chờn.
Skeleton ESP-IDF nhỏ
API ESP-Modbus v2 cụ thể sẽ phụ thuộc version component, nên đoạn dưới đây minh họa cấu trúc chứ không phải app copy-paste chạy ngay:
typedef struct {
uint16_t temperature_x10;
bool pump_request;
TickType_t received_at;
bool valid;
} plc_snapshot_t;
static void control_task(void *arg) {
const TickType_t period = pdMS_TO_TICKS(100);
TickType_t last = xTaskGetTickCount();
for (;;) {
plc_snapshot_t snapshot = read_latest_snapshot();
bool fresh = snapshot.valid &&
(xTaskGetTickCount() - snapshot.received_at) < pdMS_TO_TICKS(500);
set_pump_output(fresh && snapshot.pump_request);
vTaskDelayUntil(&last, period);
}
}
Điểm quan trọng nằm ở chỗ: chu kỳ control cố định, có kiểm tra freshness và có quyết định output an toàn. Khi đưa vào máy thật, trạng thái an toàn và timeout phải xuất phát từ yêu cầu của quy trình, không phải từ ví dụ này.
Lệnh ghi cần được bảo vệ kỹ hơn lệnh đọc
Đọc nhiệt độ thường ít rủi ro. Ghi coil hoặc holding register có thể làm máy đổi trạng thái. Mình thích đặt write sau một command path rõ ràng:
- Kiểm tra range và mode trước khi gửi.
- Yêu cầu local permission hoặc machine-ready bit.
- Ghi log tác nhân yêu cầu lệnh.
- Giới hạn tốc độ retry.
- Kiểm tra response và khi phù hợp thì đọc lại state.
- Không xem response Modbus thành bằng chứng rằng actuator vật lý đã hoàn tất hành động.
Nếu ESP32 mở giao diện web hoặc MQTT, authentication và network segmentation cũng là một phần của control design. Một endpoint không có password nhưng có quyền ghi bit bơm không còn là prototype vô hại khi đã nối vào mạng nhà máy.

Hãy kiểm tra segment RS-485 bằng adapter đã biết hoạt động trước khi debug firmware ứng dụng.
Thứ tự debug giúp đỡ mất thời gian
Khi giá trị đọc ra sai, mình đi từ dưới lên:
- Nguồn và reference chung.
- Cực A/B và termination.
- UART pin và điều khiển hướng của transceiver.
- Baud rate, parity, stop bit và slave ID.
- Function code và địa chỉ zero-based.
- Độ rộng register, scale, byte/word order.
- Timing task, timeout và reconnect.
Đừng đổi năm thông số cùng lúc. Một USB-RS-485 adapter và một tool test Modbus đơn giản thường đáng giá hơn một giờ đoán mò trong firmware.
Kết luận
ESP32 phù hợp để làm IIoT edge node vì rẻ, có kết nối và dễ thử nghiệm. Phần đáng tin cậy lại đến từ ranh giới: để PLC giữ logic máy có tính quyết định, xem register map như một bản hợp đồng, tách control task khỏi network và làm cho dữ liệu stale trở nên rõ ràng.
Cách này dễ mở rộng hơn một demo chỉ poll register trong loop() rồi bật output mỗi khi có packet về.
Tài liệu tham khảo
Chia sẻ
Tiếp tục khám phá
Đọc tiếp
Bài viết liên quan
ESP32 Memory Management Deep Dive: SRAM, PSRAM, Heap và Stack FreeRTOS
Bài hướng dẫn thực tế về layout SRAM, cách dùng PSRAM, phân mảnh heap, cấp phát stack cho task FreeRTOS và các mẹo tối ưu bộ nhớ hữu ích trong firmware ESP32.
ESP32 Beginner Guide – P2: Cài đặt và nạp code đầu tiên
Cài Arduino IDE, thêm Arduino-ESP32, chọn đúng board và Port, nạp sketch đầu tiên rồi xử lý lỗi Failed to connect trên ESP32, ESP32-C3 và ESP32-S3.
18 lỗi ESP32 thường gặp – Phần 2: Các lỗi nghiêm trọng
Cách mình đọc backtrace và xử lý crash, watchdog, heap, stack, FreeRTOS, Wi-Fi, ADC, deep sleep và OTA trên ESP32 theo một checklist runtime dễ áp dụng.