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.
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
Ở 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.
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ầng | Ví dụ | Chở gì | Độ trễ điển hình |
|---|---|---|---|
| I/O số 24 V | TRIGGER, STROBE, OK/NG ra van loại | Vài bit, sự kiện cần chính xác thời gian | Dướ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, S7 | Trạ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 byte | Vài ms đến vài chục ms |
| Mạng IT / tích hợp | OPC UA, TCP socket, REST, cơ sở dữ liệu | Tọa độ robot, chuỗi mã đọc được, dữ liệu cho MES, ảnh | Hà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 PLC | Mạng I/O chu kỳ chính | Đọc/ghi bộ nhớ theo yêu cầu | Phần mềm cấu hình | Ghi chú cho kỹ sư vision |
|---|---|---|---|---|
| Siemens (S7-1200, S7-1500…) | PROFINET IO | Giao thức S7 (PUT/GET), Open User Communication (TCP/UDP), OPC UA (S7-1500) | TIA Portal | Camera 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 tag | Studio 5000 Logix Designer | Nhậ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/3 | PC 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 message | Sysmac Studio / CX-Programmer | Camera 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ện | Tù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ạy | Mộ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 gian | Tấ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 PLC | Dữ 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ước | Nhỏ, 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ới | Bit 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 link | PROFINET 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ểm | Nhược điểm |
|---|---|---|
| Modbus TCP | Mở, đơ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ỗi | Chỉ 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 |
| PROFINET | Chuẩ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/IP | Chuẩ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 CIP | Cầ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 socket | Dữ liệu có cấu trúc, chuỗi, tọa độ; OPC UA có bảo mật | Khô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.
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
- 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.
- 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.
- 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ượng | Nguyên nhân | Cách xử lý |
|---|---|---|
| Số đo trên PLC là số khổng lồ hoặc rất nhỏ | Sai thứ tự byte/word | Gử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 sai | Lệch offset: byte đệm căn lề, bảng hai bên khác phiên bản | Dù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 echo | CycleEcho + PLC so với số chu kỳ đã gửi |
| Phần mềm vision treo nhưng PLC vẫn thấy READY | Stack 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ạng | Tê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ập | Chưa bật PUT/GET, DB đang ở chế độ tối ưu | Nhờ 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 ms | Mạ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ông | Tá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:
| Offset | S7 | Modbus | Kiểu | Tên |
|---|---|---|---|---|
| 16 | %ID116 | HR 18-19 | REAL | Length |
| 20 | %IW120 | HR 20 | INT | RecipeEcho |
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ố
>/<trongstruct, đặ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
- 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.


