Modbus Coil과 Register란? Discrete Input·Holding Register·주소 체계 이해하기

Modbus Coil과 Register란? Discrete Input·Holding Register·주소 체계 이해하기#

1장 Modbus에서는 장비 데이터를 어떻게 표현할까#

Modbus 장비를 처음 연동하면 다음과 같은 표현을 만나게 됩니다.

Coil

Discrete Input

Input Register

Holding Register

처음 보면 Database의 Table이나 Program 변수처럼 느껴질 수 있습니다.

실제로는 장비 내부 Data를 외부에서 읽고 쓰기 위한 논리적인 Data 영역이라고 생각하면 이해하기 쉽습니다.

예를 들어 주차관제 Controller가 다음 상태를 가지고 있다고 하겠습니다.

차단기 열림 여부

차량 Sensor 감지 여부

Motor 전류

자동 닫힘 시간

이를 Modbus Data Model로 표현한다면 제조사는 다음처럼 Mapping할 수 있습니다.

차단기 Open Command
→ Coil

차량 감지 Sensor
→ Discrete Input

Motor 전류
→ Input Register

자동 닫힘 시간
→ Holding Register

중요한 점은 이것이 예시 Mapping이라는 것입니다.

Modbus가 특정 주소에 반드시 특정 업무 Data를 넣으라고 정하는 것은 아닙니다.

실제 의미는 장비 제조사의 Register Map이 결정합니다.


2장 Modbus의 네 가지 기본 데이터 영역#

공식 Modbus Data Model은 네 가지 주요 Table을 정의합니다. Coil과 Discrete Input은 1Bit 단위이고, Input Register와 Holding Register는 16Bit Word 단위입니다. Coil과 Holding Register는 읽기·쓰기가 가능하고, Discrete Input과 Input Register는 읽기 전용으로 정의됩니다.

정리하면 다음과 같습니다.

데이터 영역 크기 기본 접근 대표 용도 예
Coil 1Bit Read / Write 출력·ON/OFF 제어
Discrete Input 1Bit Read Only 접점·Sensor 상태
Input Register 16Bit Read Only 측정값
Holding Register 16Bit Read / Write 설정값·운전값

이 네 가지를 먼저 구분하면 Modbus 문서가 훨씬 읽기 쉬워집니다.


3장 Coil은 1Bit 읽기·쓰기 영역이다#

Coil은 기본적으로:

0
또는
1

값을 갖습니다.

그래서:

OFF / ON

False / True

닫힘 / 열림

같은 두 가지 상태를 표현하기 좋습니다.

예를 들어 제조사가 다음처럼 정의할 수 있습니다.

Coil 0
→ Gate Open Command

Coil 1
→ Warning Lamp

Coil 2
→ Buzzer

Server에서 특정 Coil에 1을 쓰면 해당 기능을 동작시키도록 장비를 설계할 수 있습니다.


Coil이 반드시 실제 Relay라는 뜻은 아니다#

중요합니다.

Coil이라는 이름 때문에:

Coil
=
실제 Relay 출력

으로 이해하기 쉽습니다.

하지만 Modbus Data Model에서 Coil은 논리적인 Read/Write Bit 영역입니다.

실제 Hardware Output과 연결할 수도 있고, Firmware 내부의 Flag로 사용할 수도 있습니다.

즉 실제 의미는 제조사가 결정합니다.


4장 Discrete Input은 1Bit 읽기 전용 영역이다#

Discrete Input 역시:

0
또는
1

값입니다.

하지만 Coil과 달리 기본적으로 읽기 전용입니다.

예를 들어 주차관제 장비라면:

Vehicle Sensor

Gate Open Sensor

Gate Close Sensor

Safety Sensor

Emergency Input

등의 상태를 나타내도록 만들 수 있습니다.

예:

Discrete Input 10

0
→ 차량 없음

1
→ 차량 감지

처럼 사용할 수 있습니다.


Coil과 Discrete Input의 핵심 차이#

간단하게 기억하면:

Coil
→ 읽고 쓸 수 있는 Bit

Discrete Input
→ 읽기 전용 Bit

입니다.

다만 실제 Hardware 동작은 장비 제조사의 구현에 따라 달라질 수 있습니다.


5장 Input Register는 16Bit 읽기 전용 영역이다#

Input Register는 기본적으로 16Bit Word입니다.

즉 하나의 Register는:

16 Bit
=
2 Bytes

입니다.

값 범위를 Unsigned Integer로 단순하게 보면:

0 ~ 65535

를 표현할 수 있습니다.

예를 들어:

Input Register 0
→ Motor 전류

Input Register 1
→ 내부 온도

Input Register 2
→ 공급 전압

처럼 Sensor나 Measurement Data를 넣을 수 있습니다.


센서값이 실제 값과 똑같이 저장된다는 뜻은 아니다#

예를 들어 Register 값이:

253

이라고 하겠습니다.

이것이 실제:

253°C

라는 뜻은 아닙니다.

장비 Manual에서:

Scale = 0.1

이라고 정의했다면:

253 × 0.1
=
25.3°C

로 해석해야 합니다.

그래서 Register를 읽을 때는 주소뿐 아니라:

Data Type

Scale

Unit

도 반드시 확인해야 합니다.


6장 Holding Register는 가장 자주 만나는 영역이다#

Holding Register 역시 16Bit Word이지만 Read/Write가 가능합니다.

그래서 산업 장비에서는 매우 다양하게 사용됩니다.

예:

운전 Mode

Timeout

Threshold

장비 설정

Counter

상태값

Calibration 값

등입니다.

예를 들어:

Holding Register 100

Value = 5

이고 제조사 문서가:

Auto Close Time
Unit = second

이라고 정의했다면:

차단기 자동 닫힘 시간
=
5초

로 해석할 수 있습니다.


Holding Register라고 반드시 설정값인 것은 아니다#

Holding Register를 흔히 설정값 영역으로 사용하지만 Modbus 규격이:

Holding Register
=
설정값만 저장

이라고 강제하는 것은 아닙니다.

제조사는 장비 내부 구조에 따라 상태값이나 계산값을 Holding Register에 Mapping할 수도 있습니다.

따라서 이름보다 Register Map을 믿어야 합니다.


7장 네 영역과 Function Code를 연결해보자#

Modbus에서는 어떤 Data 영역을 읽거나 쓸지 Function Code로 구분합니다.

대표적인 Function은 다음과 같습니다.

Function Code 기능
0x01 Read Coils
0x02 Read Discrete Inputs
0x03 Read Holding Registers
0x04 Read Input Registers
0x05 Write Single Coil
0x06 Write Single Register
0x0F Write Multiple Coils
0x10 Write Multiple Registers

이를 Data Model과 연결하면:

Coil
→ FC01 / FC05 / FC0F

Discrete Input
→ FC02

Holding Register
→ FC03 / FC06 / FC10

Input Register
→ FC04

가 됩니다.


8장 같은 주소 0인데 왜 서로 다른 데이터가 나올까#

초보자가 가장 혼란스러워하는 부분입니다.

다음 요청을 생각해보겠습니다.

FC01
Address 0

그리고:

FC03
Address 0

둘 다 Address는 0입니다.

하지만 같은 Data가 아닙니다.

첫 번째:

FC01 + Address 0
→ Coil 0

두 번째:

FC03 + Address 0
→ Holding Register 0

입니다.

즉 Function Code와 Address를 함께 봐야 실제 Data 위치를 알 수 있습니다.


9장 00001·10001·30001·40001은 무엇인가#

오래된 Modbus 문서나 PLC Manual에서는 다음과 같은 표기를 자주 볼 수 있습니다.

00001
→ Coil

10001
→ Discrete Input

30001
→ Input Register

40001
→ Holding Register

이 표기법은 어떤 Data 영역인지 사람이 쉽게 구분하기 위한 Reference Number 방식입니다.

하지만 실제 Modbus PDU에서 전달되는 Address는 별개의 Offset입니다.

공식 Modbus Protocol의 논리 주소는 0부터 시작합니다. 예를 들어 Coil 1부터 16까지를 읽을 때 PDU에서는 Address 0부터 15까지 사용합니다.


40001과 Address 0의 관계#

대표적인 전통적 표기에서는:

40001
→ Holding Register 첫 번째 항목

이고 실제 요청에서는:

Starting Address
0x0000

이 될 수 있습니다.

다음 Register:

40002
→ Address 1

입니다.

그래서 Manual에는:

40001

이라고 되어 있는데 Program에서는:

address=0

을 사용해야 하는 상황이 생깁니다.


10장 그렇다고 항상 주소에서 1을 빼면 안 된다#

여기서 매우 중요한 함정이 있습니다.

제조사 A:

Register
40001
40002
40003

처럼 표기할 수 있습니다.

제조사 B:

Address
0
1
2

라고 직접 Protocol Offset을 적을 수도 있습니다.

제조사 C:

Register
1
2
3

이라고 적으면서 내부적으로 0-based Offset을 요구할 수도 있습니다.

따라서:

Manual Address - 1

을 무조건 적용하면 안 됩니다.

먼저 문서를 확인합니다.

Reference Number인가?

Protocol Offset인가?

0-based인가?

1-based인가?

이 네 가지 질문이 중요합니다.


11장 16Bit Register 하나로 모든 데이터를 표현할 수는 없다#

Register 하나는 16Bit입니다.

그런데 현실에서는:

32Bit Integer

32Bit Unsigned Integer

IEEE 754 Float

64Bit Integer

Double

문자열

등도 필요합니다.

그래서 여러 Register를 결합합니다.

예를 들어:

Register 100
12 34

Register 101
56 78

두 Register를 합치면 32Bit Data를 만들 수 있습니다.

문제는 순서입니다.


12장 Byte Order와 Word Order를 구분해야 한다#

다음 4 Byte가 있다고 하겠습니다.

12 34 56 78

두 Register로 보면:

Register A
12 34

Register B
56 78

입니다.

어떤 장비는 그대로:

12 34 56 78

순서로 해석합니다.

다른 장비는 Word 위치를 바꾸어:

56 78 12 34

형태로 다룰 수 있습니다.

또 Device와 Library 구현에 따라 Byte 처리까지 별도로 고려해야 할 수 있습니다.

그래서 실무에서는 단순히:

Big Endian인가?
Little Endian인가?

만 묻기보다:

Byte Order는?

Word Order는?

를 각각 확인하는 편이 좋습니다.


Float 값이 이상하게 보이는 대표적인 이유#

장비 Manual에는:

Temperature
25.5

라고 되어 있는데 Program에서는:

-2.315e-31

같은 이상한 값이 나올 수 있습니다.

이럴 때 확인합니다.

Register Address

Register Count

Signed / Unsigned

Integer / Float

Byte Order

Word Order

Scale

대부분 Modbus 자체보다 Data Mapping을 잘못 해석한 문제입니다.


13장 주차관제 장비를 하나 Mapping해보자#

가상의 Gate Controller Register Map을 만들어보겠습니다.

영역 Address 의미 접근
Coil 0 Gate Open R/W
Coil 1 Gate Close R/W
Discrete Input 0 Vehicle Detected Read
Discrete Input 1 Gate Fully Open Read
Discrete Input 2 Gate Fully Closed Read
Input Register 0 Motor Current Read
Holding Register 0 Auto Close Time R/W
Holding Register 1 Operation Mode R/W

차량이 들어왔다고 하겠습니다.

Discrete Input 0
=
1

중앙 Controller가 Gate를 열기 위해:

Coil 0
=
1

을 쓸 수 있습니다.

그리고 실제로 Gate가 열렸는지는:

Discrete Input 1
=
1

로 확인할 수 있습니다.


Command와 실제 상태를 분리한다#

여기서 중요한 설계 원칙이 나옵니다.

Coil 0 = 1

을 썼다고 해서:

Gate가 실제로 열렸다.

는 뜻은 아닙니다.

그것은 단지:

Open Command를 전달했다.

는 의미일 수 있습니다.

실제 물리 상태는:

Gate Open Sensor

에 Mapping된 Discrete Input을 다시 확인해야 합니다.

즉:

Command
→ Coil

Actual State
→ Discrete Input

처럼 분리하면 더 안전한 시스템을 만들 수 있습니다.


14장 Modbus TCP Packet에서도 결국 Function과 Address를 본다#

Modbus TCP의 구조를 단순화하면:

MBAP Header
+
PDU

입니다.

PDU에는:

Function Code
+
Function Data

가 들어갑니다.

예를 들어 Read Coils 요청은:

Function Code
0x01

Starting Address
0x0000

Quantity
0x0008

처럼 구성됩니다.

공식 사양에서 FC01은 시작 주소와 읽을 Coil 개수를 지정하며 Coil은 Protocol에서 0부터 Addressing됩니다.


15장 Python으로 Coil을 읽어보자#

현재 PyModbus Stable 문서에서는 Client를 다음처럼 가져옵니다.

from pymodbus.client import ModbusTcpClient

그리고 최신 API에서는 장비 식별자에 device_id를 사용합니다. PyModbus는 3.10에서 기존 slave=를 device_id=로 변경했습니다.

시험 환경에서:

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient(
    "192.168.0.50",
    port=502
)

if client.connect():
    result = client.read_coils(
        0,
        count=8,
        device_id=1
    )

    if not result.isError():
        print(result.bits)

client.close()

이 요청은:

Starting Address
0

Quantity
8

개의 Coil을 읽습니다.

실제 Address 0이 무엇을 의미하는지는 장비 Register Map에서 확인해야 합니다.


Holding Register도 읽어보자#

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient(
    "192.168.0.50",
    port=502
)

if client.connect():
    result = client.read_holding_registers(
        0,
        count=2,
        device_id=1
    )

    if not result.isError():
        print(result.registers)

client.close()

이 경우 Function Code는:

0x03
Read Holding Registers

입니다.


16장 여러 Register를 하나의 32Bit 값으로 변환할 수 있다#

최근 PyModbus에서는 Register 배열을 정수나 Float 같은 값으로 변환하는 기능도 제공합니다.

예를 들어 두 Holding Register를 읽고 32Bit Integer로 해석할 수 있습니다.

개념적으로:

Register 10
+
Register 11
↓
INT32

입니다.

다만 Library가 변환 기능을 제공한다고 해서 제조사 데이터 형식을 자동으로 알아내는 것은 아닙니다.

장비 문서의:

INT32

UINT32

FLOAT32

Byte Order

Word Order

규칙과 일치시켜야 합니다.


17장 Coil 여러 개를 읽으면 Bit 단위로 Packing된다#

FC01로 여러 Coil을 읽으면 Response에서는 Coil 상태가 Byte 내부 Bit로 Packing됩니다.

공식 Modbus 사양에서는 첫 번째 요청 Coil이 Response Data의 첫 Byte에서 LSB부터 배치되고 이후 Coil들이 이어집니다.

예를 들어 8개 Coil 상태가:

1
0
1
0
0
0
0
0

이라면 하나의 Byte 안에 Bit 형태로 전달됩니다.

따라서 Raw Packet을 분석할 때는:

Byte 값

만 보는 것이 아니라:

Bit 위치

까지 풀어봐야 합니다.


18장 Coil을 쓸 때는 더욱 조심해야 한다#

읽기 작업:

Read Coils

은 상대적으로 안전합니다.

하지만:

Write Single Coil

Write Multiple Coils

은 실제 Hardware 동작으로 이어질 수 있습니다.

예를 들어:

Gate Open

Motor Start

Relay ON

같은 기능에 Mapping되어 있을 수 있습니다.

따라서 운영 장비에서는 단순히:

주소가 맞는 것 같다.

는 이유만으로 Write 요청을 보내서는 안 됩니다.

먼저:

Register Map 확인

운영자 승인

시험 환경 검증

물리적 안전 조건 확인

이 필요합니다.


19장 잘못된 주소를 읽었을 때는 어떤 일이 생길까#

예를 들어 Application은:

Holding Register 100

을 읽어야 하는데 Offset 계산을 잘못해서:

Holding Register 99

를 읽었습니다.

장비가 해당 Register를 제공한다면 오류 없이 값이 돌아올 수도 있습니다.

이게 더 위험합니다.

Exception이 발생하지 않는다.
↓
개발자는 정상이라고 생각한다.
↓
하지만 전혀 다른 값을 사용한다.

따라서 단순히:

Modbus Response 성공

만 확인해서는 안 됩니다.

값의 의미와 범위도 검증해야 합니다.


20장 값의 유효 범위를 함께 관리하면 좋다#

예를 들어 Motor Temperature라면:

정상 예상 범위
-20 ~ 120°C

라고 할 수 있습니다.

그런데 읽은 값이:

32767°C

라면 통신 자체는 성공했더라도:

Address 오류

Data Type 오류

Scale 오류

Endian 오류

장비 Error Value

를 의심할 수 있습니다.

즉:

통신 성공
+
업무 값 Validation

을 함께 해야 합니다.


21장 Modbus 장애는 먼저 네 가지 질문으로 줄일 수 있다#

첫 번째#

어떤 Function Code인가?

두 번째#

어느 Address인가?

세 번째#

몇 개의 Bit 또는 Register를 읽는가?

네 번째#

그 Byte를 어떤 Data Type으로 해석하는가?

이 네 가지가 맞지 않으면 Network가 정상이어도 데이터는 제대로 나오지 않습니다.


22장 자주 발생하는 주소 문제#

현장에서 특히 많이 만나는 문제입니다.

하나씩 밀린 값#

원하는 값
Register 40001

실제 읽은 값
40002

주소 기준이 0-based와 1-based 사이에서 어긋났을 가능성이 있습니다.

모든 값이 0#

가능한 원인:

잘못된 Register 영역

잘못된 Address

잘못된 Unit ID

실제 장비 값이 0

등입니다.

일부 주소에서 Exception#

해당 Address가 구현되어 있지 않거나 요청 범위가 유효하지 않을 수 있습니다.


23장 자주 발생하는 데이터 타입 문제#

음수가 큰 양수로 보인다#

예:

Raw
65526

실제로는 Signed 16Bit:

-10

일 수 있습니다.

값이 10배 또는 100배 차이 난다#

Scale 문제일 수 있습니다.

Raw
253

Scale
0.1

Actual
25.3

Float가 이상한 값으로 나온다#

Word Order

Byte Order

Register Address

를 확인합니다.

문자열이 깨진다#

Register 내부의 문자 Encoding과 Byte Order를 확인합니다.


24장 RTU에서 값이 이상하면 물리 계층도 확인한다#

Modbus RTU에서:

CRC Error

Timeout

간헐적인 이상 Frame

이 반복된다면 Register Mapping보다 먼저 Physical Layer 문제를 확인해야 할 수도 있습니다.

확인:

RS-485 A/B

Baud Rate

Parity

Stop Bit

Cable

Termination

Bias

Noise

Ground Potential

입니다.

Data Type 문제는 통신이 정상적으로 이루어진 뒤 값 해석 단계에서 주로 나타납니다.


25장 TCP에서는 Network와 Register 문제를 분리한다#

다음과 같이 구분하면 좋습니다.

TCP Connection 실패
↓
IP / Routing / VLAN / Port / Firewall 확인

TCP는 연결되지만:

Modbus Exception
↓
Function / Address / Quantity 확인

Response도 정상인데 값이 이상하다면:

Data Type / Scale / Byte Order / Word Order 확인

입니다.

즉 장애를 계층별로 나눕니다.


26장 현장 판단표#

증상 먼저 확인할 항목
Coil을 읽을 수 없음 FC01·주소·Unit ID
Discrete Input이 항상 0 Mapping·실제 Sensor 상태
Register 값이 한 칸씩 밀림 0-based·1-based
값이 비정상적으로 큼 Signed·Unsigned·Scale
Float 값이 깨짐 Word Order·Byte Order
FC03은 되지만 FC04 실패 Register 영역·Device 지원 여부
일부 Register만 Exception Register Map·요청 범위
RTU에서 CRC 오류 배선·Noise·Serial 설정
TCP 연결은 되지만 Data 없음 Function·Address·Unit ID
Write 후 장비가 엉뚱하게 동작 주소 Mapping·Data Type·안전 Logic

27장 현장 체크리스트#

□ Coil·Discrete Input·Input Register·Holding Register 중 어느 영역인가?

□ Function Code는 맞는가?

□ 문서의 주소는 Reference Number인가 Protocol Offset인가?

□ 0-based와 1-based를 확인했는가?

□ 읽는 개수는 맞는가?

□ Register 하나인지 두 개 이상인지 확인했는가?

□ Signed인지 Unsigned인지 확인했는가?

□ Integer인지 Float인지 확인했는가?

□ Scale 값이 존재하는가?

□ Unit이 무엇인가?

□ Byte Order를 확인했는가?

□ Word Order를 확인했는가?

□ 값의 정상 범위를 알고 있는가?

□ Write 가능한 영역인지 확인했는가?

□ 실제 Hardware Command와 연결된 Address인지 확인했는가?

□ 운영 장비 Write 전에 시험 환경에서 검증했는가?

28장 자기 점검#

Coil과 Discrete Input의 차이는 무엇인가#

둘 다 1Bit 영역이지만 Coil은 Read/Write이고 Discrete Input은 Read Only입니다.

Holding Register와 Input Register의 차이는 무엇인가#

둘 다 기본적으로 16Bit Word이지만 Holding Register는 Read/Write이고 Input Register는 Read Only입니다.

문서에 40001이라고 적혀 있는데 왜 코드에서는 0을 사용할 수 있는가#

40001 같은 값은 전통적인 Register Reference 표기일 수 있고 실제 Modbus Protocol Address는 0-based Offset을 사용하기 때문입니다.

32Bit 값을 읽을 때 왜 Register 두 개가 필요한가#

Modbus Register 하나가 16Bit이므로 32Bit 값을 표현하려면 일반적으로 두 개의 Register가 필요합니다.

Response는 정상인데 값이 이상하다면 무엇을 봐야 하는가#

주소 Mapping, Signed·Unsigned, Data Type, Scale, Byte Order와 Word Order를 확인해야 합니다.

29장 이 글을 마치며#

Modbus의 Data Model을 처음 보면:

Coil

Discrete Input

Input Register

Holding Register

라는 용어가 복잡하게 느껴집니다.

하지만 핵심은 간단합니다.

1Bit
├─ Coil
│  └─ Read / Write
│
└─ Discrete Input
   └─ Read Only

16Bit
├─ Holding Register
│  └─ Read / Write
│
└─ Input Register
   └─ Read Only

여기에 Function Code가 결합됩니다.

FC01
→ Coil 읽기

FC02
→ Discrete Input 읽기

FC03
→ Holding Register 읽기

FC04
→ Input Register 읽기

그리고 실제 현장에서는 여기에 한 단계가 더 필요합니다.

Function Code
+
Address
+
Register Count
+
Data Type
+
Scale
+
Byte / Word Order
+
제조사 Register Map

까지 맞아야 실제 값을 올바르게 해석할 수 있습니다.

특히 다음 다섯 가지를 기억하면 됩니다.

Coil과 Discrete Input은 1Bit Data이고 Holding Register와 Input Register는 기본적으로 16Bit Word입니다.

같은 Address 0이라도 Function Code가 다르면 전혀 다른 Data 영역을 가리킬 수 있습니다.

40001 같은 Register 표기와 실제 Protocol Address를 혼동하면 한 칸씩 밀린 데이터를 읽는 문제가 발생할 수 있습니다.

32Bit Integer와 Float는 여러 Register를 결합하므로 Byte Order뿐 아니라 Word Order도 확인해야 합니다.

Modbus Response가 성공했다고 값까지 올바르다는 뜻은 아니므로 Data Type·Scale·범위까지 검증해야 합니다.

결국 Modbus Data를 읽는 핵심은 Register 번호를 외우는 것이 아닙니다.

어떤 Function Code로 어떤 Data 영역의 어느 Address를 읽고, 돌아온 16Bit Word를 제조사가 정의한 Data Type과 순서에 따라 어떻게 실제 값으로 변환하는지를 이해하는 것이 핵심입니다.

이 페이지의 목차