⭐ Vision Engineer · Chặng 9Bài 2/9Bài học~45 phút

Chuẩn giao tiếp vision – PLC: Modbus TCP, EtherNet/IP, PROFINET

Chọn giao thức theo hãng PLC, cyclic I/O và explicit messaging, GSDML/EDS, bảng dữ liệu trao đổi và thứ tự byte với struct trong Python.

Chuẩn giao tiếp vision – PLC: Modbus TCP, EtherNet/IP, PROFINET

Học xong bài này, bạn sẽ

  • Chọn giao thức theo hãng PLC
  • Hiểu ưu nhược điểm của từng chuẩn
  • Biết dữ liệu nào nên gửi

Nên học trước: I/O số và handshake với PLC

Nội dung bài viết
  1. 1. Ba tầng giao tiếp của một trạm vision
  2. 2. Hãng nào dùng giao thức nào
  3. 3. Cyclic I/O và explicit messaging
  4. 4. Ưu nhược điểm từng chuẩn
  5. 5. File mô tả thiết bị: GSDML và EDS
  6. 6. Dữ liệu nào nên gửi
  7. 7. Đóng gói bức điện và thứ tự byte
  8. 8. Chọn giao thức: quy trình ba câu hỏi
  9. 9. Lỗi thường gặp
  10. 10. Bài tập
  11. Tóm tắt

Ở bài "I/O số và handshake với PLC", trạm vision và PLC nói chuyện bằng vài sợi dây 24 V: TRIGGER, BUSY, DONE, OK/NG. Cách đó nhanh và chắc, nhưng chỉ chở được vài bit. Khi PLC cần biết đường kính đo được là 10,004 mm, lỗi thuộc loại nào, kết quả thuộc về sản phẩm thứ mấy, bạn cần một mạng công nghiệp. Ngoài hiện trường Việt Nam bạn sẽ gặp đủ cả: PLC Siemens nói PROFINET, PLC Rockwell nói EtherNet/IP, PLC Mitsubishi nói SLMP và CC-Link IE, PLC Omron nói EtherNet/IP và FINS, và gần như PLC nào cũng nói được Modbus TCP. Bài này cho bạn bức tranh đủ chính xác để chọn giao thức theo hãng PLC, hiểu khác biệt giữa trao đổi chu kỳ và trao đổi theo yêu cầu, và quan trọng nhất: thiết kế bảng dữ liệu trao đổi với PLC, đúng kiểu, đúng địa chỉ, đúng thứ tự byte. Bài tiếp theo, "Modbus TCP bằng Python với pymodbus", sẽ biến bảng dữ liệu đó thành code chạy được.

Mục tiêu cuối bàiChọn được giao thức phù hợp theo hãng PLC và kiểu trạm (camera thông minh hay PC vision); phân biệt cyclic I/O với explicit messaging; biết vai trò của file GSDML/EDS; lập được bảng dữ liệu (địa chỉ, kiểu, ý nghĩa) cho một trạm đo đường kính; đóng gói một bức điện kết quả bằng struct và tránh hai bẫy kinh điển: thứ tự byte và căn lề.

1. Ba tầng giao tiếp của một trạm vision

Một trạm vision thực tế thường dùng đồng thời nhiều kênh, mỗi kênh cho một việc:

TầngVí dụChở gìĐộ trễ điển hình
I/O số 24 VTRIGGER, STROBE, OK/NG ra van loạiVài bit, sự kiện cần chính xác thời gianDưới 1 ms (cộng thời gian lọc ngõ vào, chu kỳ quét PLC)
Mạng công nghiệp (fieldbus trên Ethernet)PROFINET, EtherNet/IP, Modbus TCP, CC-Link IE, SLMP, FINS, S7Trạng thái, kết quả, số đo, mã lỗi, số công thức: vài chục đến vài trăm byteVài ms đến vài chục ms
Mạng IT / tích hợpOPC UA, TCP socket, REST, cơ sở dữ liệuTọa độ robot, chuỗi mã đọc được, dữ liệu cho MES, ảnhHàng chục ms trở lên, không tất định

Bài này nói về tầng giữa. Hai bài sau trong chặng ("OPC UA cho kỹ sư Vision", "TCP socket: gửi kết quả và tọa độ cho robot") lo tầng trên. Quy tắc chung: sự kiện cần chính xác đến mili giây (trigger chụp theo vị trí, lệnh thổi khí loại sản phẩm) vẫn nên đi bằng dây I/O hoặc bằng ngõ I/O tốc độ cao của chính camera; mạng công nghiệp chở dữ liệu đi kèm.

2. Hãng nào dùng giao thức nào

Câu hỏi đầu tiên khi nhận dự án không phải "camera nào" mà là "PLC hãng gì, dòng gì". Bảng dưới là bức tranh tổng quát; mỗi dòng PLC cụ thể hỗ trợ gì (có sẵn trên CPU hay cần module, cần bật tùy chọn hay license) phải kiểm tra trong tài liệu của dòng đó.

Hãng PLCMạng I/O chu kỳ chínhĐọc/ghi bộ nhớ theo yêu cầuPhần mềm cấu hìnhGhi chú cho kỹ sư vision
Siemens (S7-1200, S7-1500…)PROFINET IOGiao thức S7 (PUT/GET), Open User Communication (TCP/UDP), OPC UA (S7-1500)TIA PortalCamera thông minh: nhập file GSDML. PC vision: thường dùng S7 PUT/GET, socket hoặc OPC UA. Dữ liệu big-endian
Rockwell / Allen-Bradley (ControlLogix, CompactLogix)EtherNet/IP (CIP, kết nối I/O class 1)CIP explicit messaging (lệnh MSG), đọc/ghi tagStudio 5000 Logix DesignerNhập file EDS, hoặc khai báo "Generic Ethernet Module" với số assembly và kích thước. Dữ liệu CIP little-endian
Mitsubishi (iQ-R, iQ-F, Q, L)CC-Link IE (Field, TSN, Field Basic)SLMP (kế thừa MC protocol, đọc/ghi thanh ghi D, M…)GX Works2/3PC vision rất hay dùng SLMP qua Ethernet thường. SLMP dạng nhị phân gửi word byte thấp trước
Omron (NJ/NX, CJ/CP)EtherNet/IP (tag data link)FINS (đọc/ghi vùng nhớ DM, CIO…), CIP messageSysmac Studio / CX-ProgrammerCamera và PC vision thường dùng EtherNet/IP tag data link hoặc FINS; NJ/NX có thêm OPC UA
Hầu hết các hãng (Delta, LS, Schneider, Inovance…)Modbus TCP (cổng 502), thường có sẵn hoặc qua khối hàm/thư việnTùy hãng"Mẫu số chung": đơn giản, mở, chỉ có thanh ghi 16 bit, tự quy ước kiểu dữ liệu

Hệ quả thực tế: camera thông minh của các hãng vision lớn thường hỗ trợ sẵn nhiều giao thức (PROFINET, EtherNet/IP, Modbus TCP, và với nhiều dòng còn có SLMP hay giao thức PLC link của các hãng Nhật), bạn chỉ cần chọn trong menu và nhập file mô tả vào PLC. Còn PC vision tự viết bằng Python/C# thì khác: làm một thiết bị PROFINET hay EtherNet/IP đúng chuẩn cần protocol stack chuyên dụng (card giao tiếp có stack sẵn, hoặc stack phần mềm có license và chứng nhận), nên trong phần lớn dự án PC vision, người ta chọn giao thức mà PC đóng vai client: Modbus TCP, S7, SLMP, FINS, OPC UA hoặc TCP socket. Khi PLC chỉ chịu PROFINET/EtherNet/IP mà PC không có stack, một gateway (ví dụ Modbus TCP ↔ PROFINET) là lời giải phổ biến.

3. Cyclic I/O và explicit messaging

Đây là khái niệm quan trọng nhất của bài, vì nó quyết định cách bạn viết chương trình:

Cyclic I/O (implicit, trao đổi chu kỳ)Explicit / acyclic messaging (theo yêu cầu)
Cách chạyMột khối byte cố định được gửi đi gửi lại liên tục theo chu kỳ, không cần ai "hỏi"Một bên gửi yêu cầu (đọc/ghi địa chỉ X), bên kia trả lời
Thời gianTất định: chu kỳ cấu hình sẵn (PROFINET update time, EtherNet/IP RPI)Không tất định, phụ thuộc tải mạng và tải CPU của PLC
Với PLCDữ liệu hiện ra trong vùng I/Q (process image) như một module I/O, chương trình PLC chỉ việc đọc/ghi địa chỉPLC phải gọi khối hàm (MSG, GET/PUT, khối Modbus client…) hoặc bên ngoài chủ động đọc/ghi vào bộ nhớ PLC
Kích thướcNhỏ, cố định (thường vài chục đến vài trăm byte mỗi chiều)Linh hoạt, mỗi yêu cầu có giới hạn riêng (Modbus: 125 thanh ghi mỗi lần đọc)
Hợp vớiBit trạng thái, kết quả, số đo mỗi chu kỳTham số, đổi công thức, đọc cấu hình, chẩn đoán
Ví dụPROFINET IO cyclic, EtherNet/IP class 1, CC-Link IE cyclic, Omron tag data linkPROFINET read/write record, CIP explicit (class 3, UCMM), Modbus TCP, S7 PUT/GET, SLMP, FINS

Modbus TCP chỉ có kiểu hỏi – đáp: PLC (hoặc PC) phải hỏi vòng (polling) đều đặn để giả lập trao đổi chu kỳ. Vì vậy với Modbus, chu kỳ cập nhật là do chương trình của bạn quyết định (ví dụ hỏi mỗi 20 ms), không phải do mạng.

Chu kỳ cập nhật và độ trễ thực tế

  • PROFINET: update time được TIA Portal tính từ send clock (mặc định 1 ms) và hệ số giảm; thiết bị khai báo chu kỳ nhỏ nhất nó chịu được trong GSDML. Camera vision thường chạy ở vài ms đến vài chục ms, đủ dư cho chu kỳ máy hàng trăm ms.
  • EtherNet/IP: RPI (Requested Packet Interval) đặt khi khai báo kết nối, thường vài ms đến vài chục ms; thiết bị giới hạn RPI nhỏ nhất trong EDS.
  • Modbus TCP / S7 / SLMP / FINS: mỗi lần hỏi – đáp thường mất vài ms trên mạng nội bộ, cộng thời gian PLC xử lý yêu cầu (nhiều PLC chỉ xử lý truyền thông ở cuối chu kỳ quét).

Ví dụ có số. Dây chuyền 60 sản phẩm/phút (1 s mỗi sản phẩm), xử lý ảnh mất 120 ms, mạng PROFINET update time 8 ms, chu kỳ quét PLC 10 ms. Trễ xấu nhất từ lúc vision có kết quả đến lúc chương trình PLC thấy nó ≈ 8 + 10 = 18 ms; tổng từ trigger đến khi PLC quyết định ≈ 120 + 18 + 18 (chiều trigger đi vào, nếu trigger cũng đi qua mạng) ≈ 156 ms, còn dư nhiều so với 1 s. Nếu thay bằng Modbus TCP với PC hỏi vòng mỗi 50 ms, phải cộng thêm tối đa 50 ms mỗi chiều: vẫn ổn ở 60 sản phẩm/phút, nhưng sẽ là vấn đề ở 600 sản phẩm/phút (100 ms mỗi sản phẩm). Luôn làm phép cộng này trước khi chọn giao thức.

4. Ưu nhược điểm từng chuẩn

ChuẩnƯu điểmNhược điểm
Modbus TCPMở, đơn giản, PLC nào cũng có; thư viện cho mọi ngôn ngữ (Python: pymodbus); dễ giả lập và gỡ lỗiChỉ có bit và thanh ghi 16 bit, phải tự quy ước số thực, chuỗi; không có chu kỳ tất định; thứ tự word của số 32 bit không được chuẩn hóa; không có bảo mật
PROFINETChuẩn của hệ Siemens; cấu hình trực quan trong TIA Portal bằng GSDML; trao đổi chu kỳ tất định; chẩn đoán tốt (đèn, tên thiết bị, cảnh báo)Thiết bị phải có stack PROFINET; mỗi thiết bị cần tên thiết bị (device name) đặt đúng; PC tự viết khó làm device
EtherNet/IPChuẩn của Rockwell và Omron (NJ/NX, CJ2), nhiều hãng khác hỗ trợ; cyclic (class 1) và explicit (class 3) trên cùng giao thức CIPCần hiểu assembly instance, RPI, kiểu kết nối; số học little-endian khác Siemens; PC làm adapter cũng cần stack
Giao thức riêng (S7, SLMP, FINS)PC đóng vai client, đọc/ghi thẳng vùng nhớ PLC (DB, thanh ghi D, DM), không cần cấu hình phần cứng phía PLC; nhiều thư viện mã nguồn mởGắn chặt với một hãng; một số cần bật quyền truy cập (ví dụ S7-1200/1500 phải cho phép PUT/GET và dùng DB không tối ưu); không tất định
OPC UA, TCP socketDữ liệu có cấu trúc, chuỗi, tọa độ; OPC UA có bảo mậtKhông dành cho I/O thời gian thực cứng; xem hai bài sau

5. File mô tả thiết bị: GSDML và EDS

Với PROFINET và EtherNet/IP, PLC phải "biết" thiết bị trước khi nói chuyện: có bao nhiêu byte vào/ra, chu kỳ nhỏ nhất bao nhiêu, có tham số gì. Thông tin đó nằm trong file mô tả do hãng thiết bị cung cấp:

  • GSDML (General Station Description Markup Language): file XML cho PROFINET, tên dạng GSDML-V2.xx-Hãng-ThiếtBị-ngày.xml. Nhập vào TIA Portal (Options → Manage GSD files), kéo thiết bị vào mạng, chọn các module dữ liệu (ví dụ "Inspection Results 32 byte") vào các slot, đặt tên thiết bị và địa chỉ I/Q bắt đầu. Tên thiết bị trên camera phải trùng tên trong dự án, nếu không PLC không nhận.
  • EDS (Electronic Data Sheet): file văn bản cho EtherNet/IP (và các mạng CIP khác). Nhập vào Studio 5000 bằng EDS Hardware Installation Tool. Nếu không có EDS, có thể khai báo Generic Ethernet Module với số assembly instance và kích thước vào/ra lấy từ tài liệu thiết bị. Ở PLC Omron, dữ liệu chu kỳ được khai báo thành tag data link trong Network Configurator / Sysmac Studio.

Hãy xin đúng phiên bản file khớp với firmware của camera. Đổi firmware mà giữ file cũ là nguyên nhân hay gặp của lỗi "module sai cấu hình" khi chạy thử.

6. Dữ liệu nào nên gửi

Bảng dữ liệu là hợp đồng giữa bạn và kỹ sư PLC. Nó nên đủ để PLC ra quyết định và chẩn đoán, nhưng không nên nhồi những thứ PLC không dùng. Cấu trúc khuyến nghị:

  • Từ trạng thái (status word): các bit READY, BUSY, DONE, ERROR, cộng một bit HEARTBEAT đảo trạng thái mỗi giây để PLC biết phần mềm vision còn sống (mạng cyclic vẫn chạy kể cả khi chương trình vision đã treo, vì stack truyền thông vẫn gửi khối dữ liệu cũ).
  • Từ kết quả (result word): một số nguyên, 0 = chưa có kết quả, 1 = OK, 2 = NG, 3 = không đo được. Đừng chỉ dùng một bit OK: bit OK = 0 không phân biệt được "NG" với "chưa có gì".
  • Số chu kỳ trả lại (cycle echo): PLC gửi số chu kỳ kèm trigger, vision trả lại đúng số đó kèm kết quả. PLC so sánh để không bao giờ dùng kết quả cũ của sản phẩm trước.
  • Mã lỗi (defect code): để HMI hiển thị lý do NG và thống kê.
  • Số đo: kiểu REAL (float32) theo mm, hoặc số nguyên đã nhân hệ số (10,004 mm → 10004 µm trong DINT) nếu muốn tránh hẳn chuyện số thực.
  • Chiều PLC → vision: từ điều khiển (TRIGGER, RESET_ERROR), số công thức (recipe), số chu kỳ.

Không nên gửi qua mạng I/O chu kỳ: ảnh, chuỗi dài, danh sách số đo thay đổi độ dài. Những thứ đó đi qua OPC UA, socket hoặc lên MES. Và nhớ quy tắc của bài trước: mỗi ô dữ liệu chỉ có một bên ghi.

Dưới đây là bảng dữ liệu cho bài toán đo đường kính trục, viết thành code để vừa là tài liệu, vừa tự tính offset và địa chỉ (bài thực hành của bài học). Giả sử khối vào/ra được cấp từ byte 100 trong PLC Siemens, và nếu dùng Modbus TCP thì vùng PLC → vision bắt đầu ở HR 0, vùng vision → PLC ở HR 10 (giống bảng ở bài pymodbus).

import struct

# (tên, mã struct, kiểu PLC, ý nghĩa) — thứ tự = thứ tự trong khối dữ liệu
VISION_DEN_PLC = [
    ("Status",     "H", "WORD", "bit0 READY, bit1 BUSY, bit2 DONE, bit3 ERROR, bit7 HEARTBEAT"),
    ("Result",     "h", "INT",  "0 = chưa có, 1 = OK, 2 = NG, 3 = không đo được"),
    ("CycleEcho",  "H", "UINT", "trả lại CycleNo của lần chụp này"),
    ("DefectCode", "h", "INT",  "0 không lỗi, 11 nhỏ, 12 lớn, 21 không thấy chi tiết"),
    ("Diameter",   "f", "REAL", "đường kính đo được, mm"),
    ("Roundness",  "f", "REAL", "độ tròn (max − min bán kính), mm"),
]
PLC_DEN_VISION = [
    ("Control",    "H", "WORD", "bit0 TRIGGER, bit1 RESET_ERROR"),
    ("RecipeNo",   "h", "INT",  "số công thức (mã sản phẩm)"),
    ("CycleNo",    "H", "UINT", "số chu kỳ, PLC tăng mỗi sản phẩm"),
    ("Spare",      "H", "WORD", "dự phòng (để 0)"),
]


def bang_dia_chi(truong, vung, byte_dau, hr_dau):
    """In bảng: offset trong khối, địa chỉ S7 nếu khối bắt đầu ở byte_dau, thanh ghi Modbus nếu bắt đầu ở hr_dau."""
    off = 0
    print(f"{'Offset':>6}  {'S7':<8} {'Modbus':<8} {'Kiểu':<5} {'Tên':<11} Ý nghĩa")
    for ten, ma, kieu_plc, y_nghia in truong:
        n = struct.calcsize(">" + ma)                 # 2 byte (WORD/INT) hoặc 4 byte (REAL)
        s7 = f"%{vung}{'W' if n == 2 else 'D'}{byte_dau + off}"
        hr = hr_dau + off // 2
        mb = f"HR {hr}" + (f"-{hr + 1}" if n == 4 else "")
        print(f"{off:>6}  {s7:<8} {mb:<8} {kieu_plc:<5} {ten:<11} {y_nghia}")
        off += n
    print(f"Tổng: {off} byte = {off // 2} word\n")
    return off


print("VISION → PLC (vùng vào của PLC, bắt đầu %IB100 / HR 10)")
bang_dia_chi(VISION_DEN_PLC, "I", 100, hr_dau=10)
print("PLC → VISION (vùng ra của PLC, bắt đầu %QB100 / HR 0)")
bang_dia_chi(PLC_DEN_VISION, "Q", 100, hr_dau=0)
VISION → PLC (vùng vào của PLC, bắt đầu %IB100 / HR 10)
Offset  S7       Modbus   Kiểu  Tên         Ý nghĩa
     0  %IW100   HR 10    WORD  Status      bit0 READY, bit1 BUSY, bit2 DONE, bit3 ERROR, bit7 HEARTBEAT
     2  %IW102   HR 11    INT   Result      0 = chưa có, 1 = OK, 2 = NG, 3 = không đo được
     4  %IW104   HR 12    UINT  CycleEcho   trả lại CycleNo của lần chụp này
     6  %IW106   HR 13    INT   DefectCode  0 không lỗi, 11 nhỏ, 12 lớn, 21 không thấy chi tiết
     8  %ID108   HR 14-15 REAL  Diameter    đường kính đo được, mm
    12  %ID112   HR 16-17 REAL  Roundness   độ tròn (max − min bán kính), mm
Tổng: 16 byte = 8 word

PLC → VISION (vùng ra của PLC, bắt đầu %QB100 / HR 0)
Offset  S7       Modbus   Kiểu  Tên         Ý nghĩa
     0  %QW100   HR 0     WORD  Control     bit0 TRIGGER, bit1 RESET_ERROR
     2  %QW102   HR 1     INT   RecipeNo    số công thức (mã sản phẩm)
     4  %QW104   HR 2     UINT  CycleNo     số chu kỳ, PLC tăng mỗi sản phẩm
     6  %QW106   HR 3     WORD  Spare       dự phòng (để 0)
Tổng: 8 byte = 4 word

Mỗi trường 2 byte chiếm một WORD (một thanh ghi Modbus), mỗi REAL chiếm DWORD (hai thanh ghi). Chú ý các REAL bắt đầu ở offset chẵn (8, 12): PLC Siemens với DB không tối ưu và nhiều thiết bị yêu cầu dữ liệu nhiều byte bắt đầu ở địa chỉ chẵn, và để REAL nằm ở offset chia hết cho 4 thì càng ít rủi ro với các hệ thống khác. Đó là lý do có trường DefectCode ở offset 6 thay vì để REAL nằm ở offset 6.

Bảng dữ liệu là tài liệu sốngLưu bảng này (dạng code như trên, hoặc Excel) trong hồ sơ dự án, đánh số phiên bản, và gửi cho kỹ sư PLC trước khi viết dòng code đầu tiên. Mọi thay đổi sau đó (thêm một số đo, đổi kiểu) phải cập nhật cả hai phía cùng lúc; để dư vài word dự phòng ngay từ đầu giúp tránh phải cấu hình lại module.

7. Đóng gói bức điện và thứ tự byte

Dù đi qua PROFINET, EtherNet/IP hay ghi vào thanh ghi Modbus, cuối cùng bảng dữ liệu là một dãy byte. Số nhiều byte (WORD, INT, REAL) có hai cách xếp:

  • Big-endian (byte cao trước): Siemens S7 và PROFINET, nội dung một thanh ghi Modbus.
  • Little-endian (byte thấp trước): dữ liệu CIP của EtherNet/IP (Rockwell, Omron), SLMP dạng nhị phân của Mitsubishi, và CPU x86/ARM của chính máy PC vision.

Mô-đun struct của Python cho phép chọn rõ ràng: tiền tố > là big-endian, < là little-endian. Đóng gói khối vision → PLC ở trên cho chu kỳ 513, kết quả OK, đường kính 10,004 mm:

import struct

DINH_DANG = "HhHhff"                  # Status, Result, CycleEcho, DefectCode, Diameter, Roundness
READY, BUSY, DONE, ERROR, HEARTBEAT = 0, 1, 2, 3, 7


def dong_goi(status_bits, result, cycle, defect, d_mm, r_mm, byte_order=">"):
    status = sum(1 << b for b in status_bits)
    return struct.pack(byte_order + DINH_DANG, status, result, cycle, defect, d_mm, r_mm)


def hex_dump(b):
    return " ".join(f"{x:02X}" for x in b)


big = dong_goi([READY, DONE], 1, 513, 0, 10.004, 0.012, ">")
little = dong_goi([READY, DONE], 1, 513, 0, 10.004, 0.012, "<")
print("Big-endian   :", hex_dump(big), f"({len(big)} byte)")
print("Little-endian:", hex_dump(little))

# PLC Siemens luôn hiểu big-endian. Nếu ta lỡ gửi little-endian:
for ten, goi in [("gửi big-endian   ", big), ("gửi little-endian", little)]:
    st, kq, ck, loi, d, r = struct.unpack(">" + DINH_DANG, goi)
    print(f"PLC (big-endian) đọc khi {ten}: Status=0x{st:04X} Result={kq} Cycle={ck} D={d:.6g} R={r:.6g}")
Big-endian   : 00 05 00 01 02 01 00 00 41 20 10 62 3C 44 9B A6 (16 byte)
Little-endian: 05 00 01 00 01 02 00 00 62 10 20 41 A6 9B 44 3C
PLC (big-endian) đọc khi gửi big-endian   : Status=0x0005 Result=1 Cycle=513 D=10.004 R=0.012
PLC (big-endian) đọc khi gửi little-endian: Status=0x0500 Result=256 Cycle=258 D=6.64664e+20 R=-1.07738e-15

Đọc kỹ dòng cuối: gửi sai thứ tự byte không có lỗi nào được báo. PLC thấy Result = 256 (không phải 1, 2 hay 3), chu kỳ 258 thay vì 513 (0x0201 bị đảo thành 0x0102), đường kính 6,6 × 1020 mm. Nếu chương trình PLC chỉ kiểm tra "Result = 2 thì loại", sản phẩm sẽ đi tiếp như OK. Đây là lý do PLC nên coi mọi giá trị ngoài danh sách hợp lệ là lỗi, và vì sao chạy thử phải có bước "gửi giá trị mồi" (ví dụ 1.0 = 3F 80 00 00, chu kỳ 513 = 02 01) rồi đối chiếu trên màn hình giám sát của PLC. Với số thực qua hai thanh ghi Modbus, còn thêm chuyện thứ tự word (ABCD hay CDAB): bài pymodbus sẽ xử lý chi tiết.

Hai bẫy nhỏ nhưng tốn cả buổi chạy thử

Bẫy 1: bit trong WORD của Siemens. Ở S7, %IW100 gồm %IB100 (byte cao) và %IB101 (byte thấp). Bit 0 của WORD (giá trị 1) vì thế nằm ở %I101.0, không phải %I100.0. Kỹ sư PLC dùng địa chỉ bit trực tiếp sẽ thấy READY "nhảy sang" byte bên cạnh. Bẫy 2: căn lề. struct không có tiền tố (hoặc @) dùng cách xếp của C trên máy bạn và tự chèn byte đệm trước số 4 byte; khối dữ liệu của PLC thì không có byte đệm đó.

import struct

BYTE_DAU = 100                                   # khối vào bắt đầu ở %IB100
ten_bit = {0: "READY", 1: "BUSY", 2: "DONE", 3: "ERROR", 7: "HEARTBEAT"}
for bit, ten in ten_bit.items():
    # WORD big-endian: bit 0..7 nằm ở byte THỨ HAI (byte thấp), bit 8..15 ở byte đầu
    byte = BYTE_DAU + (1 if bit < 8 else 0)
    print(f"bit{bit} {ten:<9} → %I{byte}.{bit % 8}")

# Cùng chuỗi kiểu, khác tiền tố: '@' (mặc định) chèn 2 byte đệm trước REAL đầu tiên
for tien_to in ["@", "<", ">"]:
    print(f"calcsize('{tien_to}HhHff') = {struct.calcsize(tien_to + 'HhHff')}")
bit0 READY     → %I101.0
bit1 BUSY      → %I101.1
bit2 DONE      → %I101.2
bit3 ERROR     → %I101.3
bit7 HEARTBEAT → %I101.7
calcsize('@HhHff') = 16
calcsize('<HhHff') = 14
calcsize('>HhHff') = 14

Với chuỗi HhHff (bỏ DefectCode), kiểu C chèn 2 byte đệm để REAL bắt đầu ở offset 8, nên khối thành 16 byte, trong khi PLC chờ 14 byte với REAL ở offset 6: mọi trường sau đó lệch 2 byte. Quy tắc: luôn ghi rõ > hoặc < trong mọi chuỗi định dạng struct dùng cho truyền thông, và tự thiết kế bảng sao cho REAL nằm ở offset chẵn như mục 6.

8. Chọn giao thức: quy trình ba câu hỏi

  1. PLC hãng gì, dòng gì, đã có những gì? Siemens → PROFINET (camera thông minh) hoặc S7/OPC UA/Modbus TCP (PC). Rockwell → EtherNet/IP. Mitsubishi → SLMP hoặc CC-Link IE. Omron → EtherNet/IP hoặc FINS. Không chắc hoặc hỗn hợp nhiều hãng → Modbus TCP. Hỏi thêm nhà máy có tiêu chuẩn nội bộ không: nhiều nhà máy FDI quy định cứng giao thức cho mọi trạm.
  2. Trạm vision là camera thông minh hay PC? Camera thông minh: chọn giao thức chu kỳ chính của PLC, nhập GSDML/EDS. PC: chọn giao thức mà PC là client, hoặc thêm card/gateway nếu bắt buộc phải là PROFINET/EtherNet/IP device.
  3. Thời gian có đủ không? Cộng: xử lý ảnh + chu kỳ mạng (hoặc chu kỳ hỏi vòng) mỗi chiều + chu kỳ quét PLC, so với thời gian cho phép của một sản phẩm (mục 3). Phần cần chính xác thời gian thì giữ trên dây I/O.

9. Lỗi thường gặp

Hiện tượngNguyên nhânCách xử lý
Số đo trên PLC là số khổng lồ hoặc rất nhỏSai thứ tự byte/wordGửi giá trị mồi, thống nhất >/< và thứ tự word bằng văn bản
Mọi trường sau một vị trí đều saiLệch offset: byte đệm căn lề, bảng hai bên khác phiên bảnDùng struct có tiền tố, in bảng offset, đối chiếu từng trường
PLC đọc kết quả của sản phẩm trướcĐọc dữ liệu ngay khi thấy DONE từ chu kỳ cũ; không có cycle echoCycleEcho + PLC so với số chu kỳ đã gửi
Phần mềm vision treo nhưng PLC vẫn thấy READYStack mạng vẫn gửi khối dữ liệu cũBit HEARTBEAT, PLC giám sát nó đổi trạng thái trong thời gian quy định
Camera PROFINET không lên mạngTên thiết bị chưa đặt hoặc khác tên trong dự án; GSDML sai phiên bảnĐặt tên thiết bị bằng TIA Portal/công cụ của hãng, dùng đúng GSDML theo firmware
PC đọc S7 bị từ chối truy cậpChưa bật PUT/GET, DB đang ở chế độ tối ưuNhờ kỹ sư PLC bật quyền truy cập và tắt "optimized block access" cho DB trao đổi
Thỉnh thoảng trễ vài trăm msMạng camera GigE chung switch với mạng điều khiển; polling quá dày làm PLC quá tải truyền thôngTách mạng (hoặc VLAN), giảm tần suất hỏi vòng, gom dữ liệu thành một lần đọc

10. Bài tập

Bài 1. Khách hàng muốn trạm đo thêm chiều dài trục (REAL, mm) và gửi số công thức đang chạy về PLC (INT) để PLC xác nhận vision đang chạy đúng công thức. Thêm hai trường vào cuối khối vision → PLC của mục 6, tính offset, địa chỉ S7 (bắt đầu %IB100) và thanh ghi Modbus (bắt đầu HR 10). Có vấn đề căn lề nào không?

Xem đáp án

Thêm ("Length", "f", "REAL", …) và ("RecipeEcho", "h", "INT", …) vào cuối danh sách rồi chạy lại bang_dia_chi:

OffsetS7ModbusKiểuTên
16%ID116HR 18-19REALLength
20%IW120HR 20INTRecipeEcho

Khối thành 22 byte. Không có vấn đề căn lề vì REAL mới bắt đầu ở offset 16 (chia hết cho 4). Nếu đặt RecipeEcho trước Length thì Length rơi vào offset 18: vẫn chẵn nên S7 chấp nhận, nhưng không chia hết cho 4. Nên thêm một word dự phòng để giữ khối tròn 24 byte (module PROFINET/assembly thường có kích thước cố định như 16, 32, 64 byte). Đừng quên cập nhật chuỗi định dạng struct thành ">HhHhfffh" và báo kỹ sư PLC đổi kích thước module.

Bài 2. PC vision đọc khối PLC → vision 8 byte từ một PLC Omron qua EtherNet/IP và nhận được 01 00 03 00 2A 00 00 00. Giải mã khối theo bảng PLC → VISION ở mục 6. Nếu lập trình viên lỡ giải mã big-endian thì sẽ thấy gì?

Xem đáp án
import struct

goi = bytes.fromhex("01 00 03 00 2A 00 00 00")
dung = struct.unpack("<HhHH", goi)              # CIP: little-endian
sai = struct.unpack(">HhHH", goi)
print("little-endian (đúng):", dung, "→ TRIGGER =", dung[0] & 1)
print("big-endian (sai)    :", sai, "→ TRIGGER =", sai[0] & 1)
little-endian (đúng): (1, 3, 42, 0) → TRIGGER = 1
big-endian (sai)    : (256, 768, 10752, 0) → TRIGGER = 0

Đúng: TRIGGER bật, công thức 3, chu kỳ 42. Giải mã sai cho Control = 256 (bit 0 tắt nên vision không bao giờ thấy trigger), công thức 768, chu kỳ 10752. Triệu chứng ngoài hiện trường: "PLC đã bật trigger mà vision không chụp".

Bài 3. Chọn giao thức cho ba trạm và giải thích ngắn: (a) PLC Siemens S7-1500, camera thông minh có PROFINET; (b) PLC Mitsubishi iQ-R, PC vision viết bằng Python; (c) dây chuyền có PLC Rockwell và PLC Delta, một PC vision cần gửi kết quả cho cả hai.

Xem đáp án

(a) PROFINET: nhập GSDML của camera vào TIA Portal, chọn module kết quả, đặt tên thiết bị; dữ liệu vào vùng I/Q như một module I/O, chu kỳ tất định. (b) SLMP qua Ethernet: PC làm client đọc/ghi thẳng thanh ghi D, không cần cấu hình phần cứng phía PLC ngoài việc mở cổng SLMP; nhớ dữ liệu word là byte thấp trước. Modbus TCP cũng là phương án nếu CPU hoặc module Ethernet của dòng đó hỗ trợ (kiểm tra tài liệu). (c) Modbus TCP là mẫu số chung đơn giản nhất cho cả hai (PC làm client hỏi vòng hai PLC, hoặc mỗi PLC làm client đọc PC). Nếu nhà máy chuẩn hóa EtherNet/IP cho Rockwell thì có thể dùng gateway, nhưng tốn thêm thiết bị và điểm hỏng. Với cả ba trạm, vẫn giữ TRIGGER và tín hiệu loại sản phẩm trên dây I/O nếu cần chính xác thời gian.

Tóm tắt

  • Chọn giao thức theo PLC: Siemens → PROFINET/S7, Rockwell → EtherNet/IP (CIP), Mitsubishi → SLMP/CC-Link IE, Omron → EtherNet/IP/FINS; Modbus TCP là mẫu số chung.
  • Cyclic I/O (PROFINET IO, EtherNet/IP class 1) gửi khối cố định theo chu kỳ tất định (update time, RPI); explicit messaging (CIP class 3, S7, SLMP, FINS, Modbus) là hỏi – đáp theo yêu cầu.
  • Camera thông minh: nhập GSDML/EDS. PC vision: thường làm client (Modbus TCP, S7, SLMP, FINS, OPC UA, socket) vì làm device cần stack chuyên dụng.
  • Bảng dữ liệu tối thiểu: status word (có heartbeat), result word, cycle echo, mã lỗi, số đo REAL; chiều ngược lại: control word, recipe, số chu kỳ. Mỗi ô chỉ một bên ghi.
  • Siemens big-endian, CIP và SLMP nhị phân little-endian; luôn ghi tiền tố >/< trong struct, đặt REAL ở offset chẵn, kiểm tra bằng giá trị mồi.
  • Cộng thời gian xử lý + chu kỳ mạng + chu kỳ quét PLC trước khi chọn; tín hiệu cần chính xác thời gian vẫn đi bằng dây I/O.

Bài tiếp theo: Modbus TCP bằng Python với pymodbus, nơi bảng dữ liệu này được đọc/ghi thật với một PLC giả chạy ngay trên máy bạn.

Bài tập thực hành

  1. Lập bảng dữ liệu (địa chỉ, kiểu, ý nghĩa) trao đổi giữa trạm vision và PLC cho bài toán đo đường kính.

Làm xong bài tập mới chuyển sang bài tiếp theo: tự viết code là cách duy nhất để nhớ lâu.

Bài tiếp theo
Ban biên tập HọcFree

Chúng tôi viết những bài hướng dẫn dễ hiểu, thực tế để ai cũng có thể tự học lập trình và ngoại ngữ, hoàn toàn miễn phí.