글 / Modbus·OPC UA

Modbus 레지스터 작업 지시 핸드셰이크

명령 코드에 인접하여 순번 레지스터를 두면 잔류한 값과 새 명령을 구분할 수 있다.

작성일 · 읽는 시간 2분

문제 정의

Modbus가 제공하는 동작은 레지스터 읽기와 쓰기뿐이며, 장비가 먼저 통신을 개시할 수도 없다. 작업을 지시하고 완료 여부를 확인하려면 어느 레지스터를 누가 언제 쓰는지 규약으로 정해야 한다. 이 규약이 핸드셰이크이다.

레지스터 배치

명령을 내리는 측이 상위이다.

주소 이름 쓰는 측 값
100 명령 코드 상위 0 없음, 1 이상은 작업 종류
101 명령 순번 상위 명령마다 1씩 증가
110 상태 장비 0 대기, 1 실행 중, 2 완료, 3 실패
111 처리한 순번 장비 수락한 명령의 순번

처리 순서

text
1. 상위  상태 0을 확인하고 명령 코드와 순번을 쓴다
2. 장비  상태를 1로 바꾸고 처리한 순번을 적는다
3. 장비  끝나면 상태를 2나 3으로 바꾼다
4. 상위  결과를 읽고 명령 코드를 0으로 지운다
5. 장비  명령 코드가 0이면 상태를 0으로 되돌린다

상위와 장비가 레지스터로 교환하는 작업 지시 다섯 단계

잔류 값 문제

명령 코드만 있으면 같은 작업을 연속으로 지시할 때 값이 변경되지 않아 새 명령인지 판별할 수 없다. 상위가 재시작하면 남아 있는 완료 표시가 어느 명령에 대한 것인지도 판별할 수 없다.

장비는 명령 순번이 처리한 순번과 다를 때만 작업을 시작하고, 상위는 처리한 순번이 자신이 쓴 명령 순번과 같을 때만 결과로 인정한다. 같은 순번으로 다시 써도 작업은 한 번만 실행된다.

상태 레지스터의 전이와 순번 비교로 작업을 시작하는 조건

pythonpymodbus 클라이언트로
import time


def run_command(client, code, seq, timeout=30):
    # 상태가 0(대기)인지는 부르는 쪽이 먼저 확인한다. 명령 코드와 순번을 한 요청으로 쓴다
    client.write_registers(100, [code, seq])
    end = time.monotonic() + timeout
    while time.monotonic() < end:
        rr = client.read_holding_registers(110, count=2)
        if not rr.isError():
            state, done_seq = rr.registers
            # 내 순번의 결과만 본다
            if done_seq == seq and state in (2, 3):
                client.write_register(100, 0)
                return state == 2
        time.sleep(0.2)
    return False