C++

ModbusTcp

다크엔지니어 2020. 4. 18. 15:02
반응형

ModbusTcp 를 개발하게 되었다. C++ 32bit 환경에서 개발을 해보기로 했으며, 실질적으로 3일정도 소요된것 같다.

아래 링크인 Github 리포지토리에 개발한 소스를 push 해 놓았다.

https://github.com/Anthony8062/ModbusTcp

 

Anthony8062/ModbusTcp

프로젝트. Contribute to Anthony8062/ModbusTcp development by creating an account on GitHub.

github.com

우선 ModbusTcp 말고도 ModbusRTU 등이 존재하고 그 중 ModbusTcp 로 개발하게 되었다.

실제적으로 실무에서도 자주 사용되는 프로토콜이다.

아래는 ModbusTcp 프로토콜의 프레임 구조 설명이다.

· MODBUS-TCP 프레임 구조

MODBUS-TCP의 필요이유는 위의 프로토콜 규격을 이용하여 Slave 즉 Server 의 adress에 접근하여 원하는 해당 data 의 값을 읽어 올 수 있다.

- MBAP Header

ModbusTcp는 MBAP(Modbus Application Protocol)와 Function code, Data로 순으로 이루어져 있다. MBAP는 총 7 Byte이고 아래와 같은 내용의 Byte값을 나타낸다.

  • Transaction ID [2Bytes] : 마스터(Client)가 최초 0x0000값 부터 통신시작 시 1씩 증가시키며 슬래이브(Server)는 그 값을 그대로 복사해서 사용한다. 쿼리및 응답에 대해 한쌍으로 작업이 이루어 졌는지를 확인하는 부분이다.

  • Protocol ID [2Bytes] : 프로토콜의 ID를 나타내며 MODBUS-TCP는 0x0000의 고정값을 사용한다.

  • Length [2Bytes] : Length 필드위치에서 프레임 마지막까지의 길이를 나타낸다. 즉 Unit ID ~ Data끝까지의 Byte의 수를 나타낸다.

  • Unit ID [1 Byte] : TPC/IP가 아닌 다른 통신선로의 연결되어있는 Slave를 구분하는 정보이다. Tcpport는 0x01로 고정이다.

 

- Function Code

Function Code는 Modbus 프로토콜에서 제공하는 명령어 집합코드로 Function Code를 이용하여 슬레이 Memory(Coil, Register )에 값을 읽어오거나 쓸수있는 서비스이다. 실제로 Function Code코드 1~127사이의 값을 사용하고있지만, Tcp Port에서는 1, 2, 4, 5, 6, 15, 16 값을 지원한다.

MODBUS 데이터 모델은 입력과 출력 그리고 비트 단위 접근과 워드 단위 접근을 기준으로 총 4가지 형태로 나누어진다.

메모리데이터모델접근형태읽기/쓰기설명

Coil Discrete Input 비트 읽기 상위장치에서 메모리 읽기 가능
Coil Coils 비트 읽기/쓰기 상위장치에서 메모리 읽고, 쓰기 가능
Register Input Registers 16비트 워드 읽기 상위장치에서 메모리 읽기 가능
Register Holding Resisters 16비트 워드 읽기/쓰기 상위장치에서 메모리 읽고, 쓰기 가능

Tcp Port는 16비트 워드영역(Resisters)과 비트영역(Coils)으로 두 개의 데이터 메모리로 나누어져 있다. 여기서 말하는 메모리는 Slave(server)장비의 메모리를 말하며 Master(Client)는 위의 평션코드를 이용하여 Slave(server)장비의 메모리를 읽거나 원하는 값으로 변경할 수 있다.

· TCPPORT의 MODBUS 데이터 메모리 구조

위의 그림 처럼 해당 Function Code에 따라 어떤 메모리를 접근할 것인지, 어떤 작업수행(Read, Write)을 행할 것인지가 나누어져 있다.

- Data

Data는 Function Code에 따라 그 구조가 조금식 달라진다. Data는 기본적으로 Start Address, Length, Byte Count, Data의 형태를 가지고 있다.

  • Start Address [2Bytes] : 접근하려는 메모리의 시작번지를 나타낸다. 2Byte로 표현되면 상위 Byte 우선순이다. (예 0x4001번지 접근 시 0x40 0x01)

  • Length [2Bytes] : 시작번지부터 값을 읽거나 쓸 길이를 나타낸다.

  • Byte Count [1Bytes] : Request, Response에따른 메모리 Data의 byte 수를 나타낸다. 즉, 읽어거나 쓸려는 메모리 데이터의 Byte의 개수를 말한다.

  • DATA [N Byte] : Request, Response에 따른 메모리 Data의 값 나타냅니다. 즉, 읽어거나 쓸려는 메모리 값이다.

 

본인이 개발하여 Github에 push 된 소스를 기반으로 한 결과를 말하자면 아래와 같다.

개요

  • Server(Slave), Client(Master) 의 통신 프로토콜로 Register Input , Write 등을 기반으로 개발
  • Multi Thread 를 사용하였으며, std::thread 이다.

작성한 코드의 문제점

  • Client 즉 Master 의 접근을 너무 정적으로 만들었다. 콘솔을 하나더 만들어 접근하게 했으면 좀 더 실용성이 있었을 것이다.
  • Server 즉 Slave 가 무한정 기다리게끔 하는것이 더 효율이 있다고 보여진다. 작성 코드에서는 Thread 를 통해 기다린다.
  • connect시 예외가 좀 아쉽다. socket errer 시 에만 예외가 되어있어는데, connect에서 time delay 가 발생되는 예외 추가가 필요해 보인다.
  • 비동기 방식으로 구현하는것이 퍼포먼스가 더 좋아보여진다. 속도측면에서
  • 실무에서는 10 m/s 단위로 통신이 진행 될텐데, 그럴경우 위에서 작성한 server 에서의 for 문 count는 문제가 될 것이다. 물론 속도때문에..
  • 동적 바인딩을 vector 로 구현했는데 list 로 하는게 더 나을수 있겠다.. 물론 위의 상황에서는 문제가 전혀 안되지만 매우 규모가 커질 경우..

Tcp 에 관련 코딩은 매우 오랜만에 해보긴했고 Modbus 라는 프로토콜은 처음이기도 해서 .. 결과적으로 좋은경험이 된 것 같다. 문제점도 잘 알았고, 물론 처음부터 구성을 좀 잘못잡은면이 크다... 수정을 하면 됬지만 그냥 했다.. 귀ㅊ....

반응형