자동화나 제어 시스템에 널리 사용되고 있는 Modbus 프로토콜은
제어 모듈과 통신할 때 많이 사용되고 있는 프로토콜입니다.
이번 포스팅은 Modbus TCP에 대해 간략히 설명하고
C#에서 Modbus를 사용하는 방법과 NModbus 라이브러리를 활용하는 방법에 대해 작성했습니다.
| Modbus TCP 란?
Modbus는 산업 자동화 및 제어 시스템에서 널리 사용되는 프로토콜로 많이 사용되고 있습니다.
장치 간 간의 데이터 전송에 사용되고 있으며
Modbus TCP란 Modbus 프로토콜의 변형으로 TCP/IP 네트워크를 통해 작동합니다.
이 프로토콜은 클라이언트 - 서버 아키텍처 기반으로 작동되며
클라이언트(마스터)는 서버(슬레이브)에게 요청을 보내고 서버는 요청에 대한 응답을 제공합니다.
| Modbus TCP 주요 특징
- 단순성 : 구현이 간단하며 다양한 장치와 시스템에 쉽게 사용
- 호환성 : TCP/IP 네트워크 기반으로, 기존의 네트워크 인프라를 활용
- 확장성 : 여러 장치와의 통신이 가능하여 대규모 시스템에도 유용
위와 같은 내용으로 Modbus TCP는 주로 센서 모듈과 통신할 때나 장비 제어, 모니터링 등으로
데이터를 통신할 때 많이 사용하고 있습니다.
| C#에서 Modbus 사용법 (직접 구현)
C#에서 Modbus 통신을 하기 위해서는 직접 구현하는 방법도 있지만
Modbus 프로토콜 자체는 데이터를 패킷 하여 송수신하는 작업을 포함하기 때문에
직접 구현하기보다는 라이브러리를 사용하는 것이 효율적입니다
만약 직접 구현한다고하면 소켓 프로그래밍을 통해 직접 프로토콜을 구현해야 합니다.
직접 프로토콜을 구현하는 간단한 기본 단계와 코드로 구현해 보겠습니다.
| Modbus 기본 단계
- 소캣 생성 : TCP 소캣 생성
- 서버 연결 : Modbus 슬레이브 연결
- 요청 패킷 생성 : 프로토콜에 맞는 요청 패킷 생성
- 패킷 전송 : 생성한 요청 패킷을 슬레이브에 전송
- 응답 수신 : 슬레이브로부터 응답 패킷 수신
- 응답 처리 : 수신한 응답을 해석하여 필요 데이터 추출
- 연결 종료 : 통신 종료 후 소켓 닫기
예제) 소켓 프로그래밍을 사용한 Modbus TCP 통신
// Modbus 슬레이브의 IP 주소와 포트
string ipAddress = "192.168.0.100";
int port = 502;
// TCP 소켓 생성
using (TcpClient client = new TcpClient(ipAddress, port))
{
NetworkStream stream = client.GetStream();
// Modbus 요청 패킷 생성 (Function Code: 0x03 - Read Holding Registers)
byte[] request = new byte[12];
request[0] = 0; // Transaction Identifier (2 bytes)
request[1] = 0; // Transaction Identifier
request[2] = 0; // Protocol Identifier (2 bytes)
request[3] = 0; // Protocol Identifier
request[4] = 0; // Length (2 bytes)
request[5] = 6; // Length (6 bytes for the rest of the message)
request[6] = 1; // Unit Identifier (슬레이브 ID)
request[7] = 3; // Function Code (Read Holding Registers)
request[8] = 0; // Starting Address (High byte)
request[9] = 1; // Starting Address (Low byte)
request[10] = 0; // Quantity of Registers (High byte)
request[11] = 1; // Quantity of Registers (Low byte)
// 요청 패킷 전송
stream.Write(request, 0, request.Length);
Console.WriteLine("요청 패킷 전송 완료.");
// 응답 수신
byte[] response = new byte[256];
int bytesRead = stream.Read(response, 0, response.Length);
Console.WriteLine("응답 패킷 수신 완료.");
// 응답 처리
if (bytesRead > 0 && response[7] == 3) // Function Code가 3인지 확인
{
// 데이터는 9번째 바이트부터 시작
int registerValue = (response[9] << 8) + response[10]; // 레지스터 값 조합
Console.WriteLine($"레지스터 값: {registerValue}");
}
else
{
Console.WriteLine("유효하지 않은 응답입니다.");
}
}
TcpClient를 사용하여 Modbus 슬레이브에 연결하고
Function Code 0x03을 사용한 홀딩 레지스터를 사용하여 요청 패킷을 생성했습니다.
stream.Write()를 사용하여 요청 패킷을 전송하였으며
stream.Read()를 통해 응답을 받습니다.
위 코드 예제는 Modbus TCP 통신을 구현할 때 가장 기본적인 흐름을 보여주는 예제이므로
실제 환경에서 쓰신다면 에러 처리나 데이터를 처리할 때 기능을 추가해야 합니다!
| C#에서 Modbus 사용법 (NModbus 라이브러리 사용)
직접 구현할 때는 저렇게 데이터 처리나 TCP Client 연결 같은 부분을 신경을 다 써줘야 하지만
NModbus 라이브러리를 사용하면 효율적으로 코드 구현을 할 수 있습니다.
예제) NModbus를 사용한 Modbus TCP 통신
Modbus 기본 단계를 따르지만 앞서 직접 구현했던 코드보다 훨씬 간략하게 구현할 수 있습니다.
1) Modbus TCP 클라이언트 생성
var tcpClient = new TcpClient("192.168.1.100", 502);
var factory = new ModbusFactory();
var modbusMaster = factory.CreateMaster(tcpClient);
TcpClient는 Modbus 서버와의 TCP 연결을 설정하며,
ModbusFactory는 IModbusMaster 객체를 생성하여 실제로 Modbus 명령을 송수신할 수 있게 해 줍니다.
2) 레지스터 읽기
// 1번 슬레이브 장치의 주소 100번부터 시작하는 5개의 레지스터를 읽습니다.
ushort[] registers = modbusMaster.ReadHoldingRegisters(1, 100, 5);
foreach (var register in registers)
{
Console.WriteLine($"Register Value: {register}");
}
Modbus 프로토콜에서 데이터를 읽는 기본 단위는 Register인데
특정 주소의 데이터를 읽으려면 ReadHoldingRegisters() 메서드를 사용합니다.
위 코드에서는 슬레이브 1번 ID의 장치의 레지스터 주소 100부터 5개의 레지스터를 읽어
registers 변수에 출력하는 예제입니다.
3) 레지스터 쓰기
ushort[] valuesToWrite = { 10, 20, 30, 40, 50 };
modbusMaster.WriteMultipleRegisters(1, 100, valuesToWrite);
반대로 데이터를 쓰려면 WriteMultipleRegisters 메서드를 사용하는데
이는 여러 개의 레지스터에 한 번에 값을 쓰는 메서드입니다.
위 코드는 슬레이브 ID 1번 장치의 레지스터 100번부터 5개의 값을 씁니다.
4) 연결 종료
tcpClient.Close();
작업이 끝나면 TCP 연결을 종료하여 리소스를 해제해야 합니다 (꼭!)
| 마무리
NModbus 라이브러리를 사용해서 간단하게 Modbus TCP 통신을 구현했습니다.
저도 장비 업체에 있으면서 온도 컨트롤러나 전력량계 같은 장치들과 통신을 할 때 자주 쓰고 있고
특히 중요한 점은 예외 처리를 잘하여 응답을 받지 못해도 코드가 죽지 않게 만드는 게 중요한 것 같습니다!
틀린 점이나 질문이 있으시면 댓글로 남겨주세요!
감사합니다 :)
'C#' 카테고리의 다른 글
C# Enum 열거형에서 Name, Value 처리하는 방법 (0) | 2024.10.06 |
---|---|
C# 배열을 메서드에 전달하면 안되는 이유 (1) | 2024.10.05 |
C# 네트워크 통신을 위한 BitConverter 클래스 이해 (0) | 2024.03.18 |
C# Byte 통신을 위한 Array 클래스 이해 (0) | 2024.03.15 |
C# List의 마지막 요소에 접근하는 3가지 방법 (0) | 2024.02.20 |