
이 글이 나한테 해당되는지 먼저 확인해보세요
이런 상황이 하나라도 해당된다면, 이 글이 도움이 될 겁니다.
- SLMP로 쓰기 명령을 보냈는데 서버 로그에는 성공(200 OK)이라고 나오는데 PLC 값이 안 바뀐다
- 값이 바뀌긴 하는데 0.5초 뒤에 원래 값으로 돌아온다
- M 디바이스에 1을 썼는데 읽어보면 0이다
- PLCLink IO Monitor에서 ON/OFF 버튼을 눌러도 아무 반응이 없다
- 소프트웨어로 PLC 값을 제어하는 방법이 궁금한데 잘못된 방향으로 가고 있는 것 같다
처음엔 버그인 줄 알았습니다.
타이밍 문제인가 해서 딜레이도 늘려봤고, 통신 재시도 로직도 추가해봤어요.
결국 원인은 PLCLink가 아니라 PLC가 돌아가는 방식 자체에 있었습니다.
용어 먼저 짚고 넘어갈게요

SLMP (Seamless Message Protocol)란
Mitsubishi PLC와 외부 기기가 이더넷으로 통신하는 프로토콜입니다.
소켓을 열고 정해진 형식의 패킷을 보내면 PLC가 응답하는 방식이에요.
배터리처럼 "읽기"와 "쓰기"를 모두 지원합니다.
래더(Ladder) 프로그램이란
PLC 안에서 실행되는 제어 로직입니다.
전기 회로도처럼 생긴 그림으로 프로그램을 짜기 때문에 "래더(사다리)"라는 이름이 붙었어요.
이 프로그램이 PLC 안에서 계속 실행되면서 입력을 읽고, 계산하고, 출력을 씁니다.
스캔 사이클(Scan Cycle)이란
PLC는 래더 프로그램을 처음부터 끝까지 실행하고, 끝나면 다시 처음부터 반복합니다.
이 한 번의 실행 과정을 "스캔"이라고 하고, 소요 시간을 "스캔 타임"이라고 해요. 보통 1~10ms 수준입니다.
PLC는 이걸 쉬지 않고 계속 반복합니다.
OUT 코일이란
래더 프로그램에서 특정 메모리(M100 같은)에 값을 쓰는 명령입니다.
[OUT M100]이 래더에 있으면 매 스캔마다 이 명령이 실행되어서 M100의 값을 덮어씁니다.
재현 방법
먼저 상황을 재현해보겠습니다. Mitsubishi GX Works로 간단한 래더를 작성합니다.
[M000] ----( OUT M100 )----
X0이 ON이면 M100을 ON, OFF이면 M100을 OFF하는 래더입니다.
이걸 PLC에 쓰고, PLCLink나 SLMP 통신 프로그램으로 M100에 1을 쓰면 어떻게 될까요?
# SLMP로 M100에 1 쓰기
await slmp_write(device="M", address=100, value=1)
print("쓰기 성공") # 200 OK
# 150ms 후 읽기
await asyncio.sleep(0.15)
result = await slmp_read(device="M", address=100)
print(f"현재 값: {result}") # 0 (또는 X0 상태에 따라 다름)
쓰기는 성공했는데 읽으면 원래 값으로 돌아와 있습니다.
처음 이걸 보면 타이밍 문제라고 생각하기 쉬워요.
왜 이렇게 되는가 : 스캔 사이클이 모든 것을 결정한다

SLMP 쓰기가 어떻게 동작하는지 이해하면 원인이 보입니다.
SLMP 쓰기는 "PLC 메모리에 직접 값을 넣는" 작업입니다.
PLC 내부의 M100이라는 메모리 주소에 1이라는 값을 씁니다.
여기까지는 성공합니다.
그런데 PLC는 래더 프로그램을 매 스캔마다 실행합니다.
스캔이 한 번 끝나는 데 1~5ms 정도 걸려요.
PLCLink가 M100에 1을 썼더라도, 그 직후 스캔이 실행되면서 [OUT M100] 명령이 다시 M100의 값을 계산해서 덮어씁니다.
시간 흐름
─────────────────────────────────────────────────────
t=0ms : PLCLink가 M100 = 1 씀 (SLMP 쓰기 성공)
t=1ms : PLC 스캔 사이클 시작
t=2ms : [OUT M100] 실행 → X0 상태에 따라 0 또는 1 덮어씀
t=3ms : 스캔 끝
t=150ms : PLCLink가 M100 읽음 → 래더가 덮어쓴 값이 나옴
요리 비유로 설명하면 이렇습니다.
냉장고 안의 물건 목록을 관리하는 로봇이 있습니다.
이 로봇은 1초마다 냉장고 문을 열고 목록을 다시 작성합니다.
내가 중간에 메모지에 "사과 5개"라고 써놔도, 1초 후에 로봇이 냉장고를 확인하고 나서 "사과 3개"로 덮어씁니다.
내가 쓴 메모는 효력이 없어요.
SLMP 쓰기가 "메모지 작성"이고, 래더 스캔이 "로봇의 냉장고 확인"입니다.
디바이스별로 상황이 다르다

모든 디바이스가 이런 문제를 겪는 건 아닙니다.
래더가 그 디바이스를 "읽기만 하느냐" vs "쓰기도 하느냐"에 따라 다릅니다.
값이 유지되지 않는 경우 : 래더가 쓰는 영역
| 디바이스 | 래더에서의 역할 | SLMP 쓰기 결과 |
|---|---|---|
| Y (출력 릴레이) | [OUT Y] 코일로 씀 | 래더가 덮어씀 |
| M (내부 릴레이) | [OUT M] 코일로 씀 | 래더가 덮어씀 |
| R (내부 릴레이) | [OUT R] 코일로 씀 | 래더가 덮어씀 |
값이 유지되는 경우 : 래더가 읽기만 하는 영역
| 디바이스 | 래더에서의 역할 | SLMP 쓰기 결과 |
|---|---|---|
| D (데이터 레지스터) | MOV 명령으로 읽음 | 유지됨 |
| W (링크 레지스터) | 링크 경유 읽음 | 유지됨 |
단, Y 디바이스는 출력 릴레이라서 실제 케이블로 연결된 장비에 영향을 줍니다.
래더가 Y를 제어하는 거라 SLMP로 직접 쓰기를 해봤자 래더가 바로 덮어씁니다.
그럼 소프트웨어로 PLC를 제어하려면 어떻게 해야 하나

답은 생각보다 단순합니다.
래더가 읽는 영역에 명령값을 쓰면 됩니다.
D 레지스터는 래더가 읽기만 하고, PLC 스캔이 값을 덮어쓰지 않습니다.
그래서 D 레지스터에 명령값을 쓰면 값이 유지됩니다.
구체적인 설계 방식입니다.
소프트웨어가 D100에 1을 씀
↓
래더가 D100을 읽음
↓
D100 = 1이면 [SET Y0] 실행
D100 = 0이면 [RST Y0] 실행
래더 쪽에서 D 레지스터를 읽어서 동작하도록 설계하는 겁니다.
소프트웨어는 D 레지스터에 명령값만 씁니다.
M이나 Y를 직접 건드리지 않아요.
# D 레지스터에 명령값 쓰기
await slmp_write(device="D", address=100, value=1)
# 래더가 D100을 읽어서 Y0을 ON/OFF
# 150ms 후 읽어도 값이 유지됨
result = await slmp_read(device="D", address=100)
print(f"D100 = {result}") # 1 (유지됨)
이 방식이 현장에서 실제로 쓰이는 구조입니다.
소프트웨어 HMI, 상위 MES 시스템 모두 이 방식으로 PLC와 통신합니다.
PLCLink에서 이 원칙이 어떻게 반영됐나
Phase 3에서 IO Monitor에 출력 쓰기 기능을 추가하면서 이 원칙을 적용했습니다.
처음엔 M 디바이스에 직접 쓰는 방식으로 구현했어요.
동작은 했는데 값이 유지가 안 됐습니다.
원인을 파악하고 나서 쓰기 가능한 디바이스를 제한했습니다.
Y 디바이스는 래더가 관리하지 않는 경우가 있어서 허용합니다.
강제 출력 용도로 쓸 수 있어요.
다만 이 경우도 래더가 Y를 제어하는 구조라면 값이 즉시 덮어써집니다.
# PLCLink 쓰기 권한 체크
WRITABLE_DEVICES = {
"Y": ["confirm", "engineer"], # 두 모드 모두 가능
"M": ["engineer"], # 엔지니어만
"R": ["engineer"], # 엔지니어만
# X는 쓰기 불가 (물리 입력)
# D/DM은 DataList 페이지에서 별도 처리
}
현장에서 실제로 D 레지스터를 통한 명령값 방식을 쓰려면, 먼저 PLC 래더 프로그램이 그렇게 설계되어 있어야 합니다.
PLCLink가 아무리 D 레지스터에 명령값을 써도,
래더가 그걸 읽어서 동작하도록 프로그래밍되어 있지 않으면 아무 반응이 없습니다.
정리 : SLMP 쓰기 전에 알아야 할 것

| 항목 | 내용 |
|---|---|
| SLMP 쓰기의 역할 | PLC 메모리에 직접 값을 넣는 것 |
| 래더 스캔 사이클 | 1~10ms마다 래더가 전체 실행됨 |
| 값이 유지 안 되는 원인 | OUT 코일이 있는 디바이스는 매 스캔마다 덮어씀 |
| 해결 방법 | 래더가 읽기만 하는 D 레지스터에 명령값 쓰기 |
| 전제 조건 | 래더가 D 레지스터를 읽어서 동작하도록 미리 설계되어 있어야 함 |
마치며
"200 OK인데 왜 값이 안 바뀌지?"라는 질문에서 시작해서, 결국 PLC가 어떻게 동작하는지를 이해해야 했습니다.
SLMP는 PLC와 통신하는 도구입니다.
그런데 PLC는 외부에서 보내는 값을 받기만 하는 장치가 아닙니다.
내부에서 래더 프로그램이 계속 실행되고 있고, 그 프로그램이 메모리를 계속 쓰고 있습니다.
소프트웨어로 PLC를 제어하려면 래더와 협력하는 방식으로 설계해야 합니다.
직접 건드리는 게 아니라, 래더가 읽는 영역에 명령값을 두고 래더가 그걸 해석해서 동작하게 하는 것.
이걸 알고 나면 HMI나 MES 시스템이 PLC와 어떻게 통신하는지도 자연스럽게 이해됩니다.
'(개인Project)_개발 > PLC-PC 연결' 카테고리의 다른 글
| [Program][보족] React 프로덕션 빌드에서만 나는 오류의 원인을 찾는 방법 (0) | 2026.05.28 |
|---|---|
| [Program][보족] PDF 매뉴얼을 알람 팝업에 연결하기까지 (3차 시도와 최종 해결) (0) | 2026.05.27 |
| [Program][Phase 3] 알람이 뜨면 매뉴얼이 바로 열린다 (PLCLink Phase 3 완성기) (0) | 2026.05.25 |
| [Program][보족] 현장 PC에서 서버를 켜면 자동으로 감시가 시작되게 만든 방법 (0) | 2026.05.23 |
| [Program][보족] 설정이 계속 날아가는 이유 (localStorage의 한계와 DB 전환 시점) (0) | 2026.05.22 |