본문으로 바로가기
homeimage
  1. Home
  2. 컴퓨터/프로그래밍
  3. MODBUS-RTU 프로토콜 쉽게 이해하기

MODBUS-RTU 프로토콜 쉽게 이해하기

· 댓글개 · 바다야크

MODBUS 프로토콜

이 글은 시리얼 통신을 잘 알고 있지만, MODBUS-RTU를 처음 접하는 분을 위해 저의 경험을 바탕으로 작성했습니다. 시리얼 통신을 꽤 오랫동안 다루었지만, MODBUS-RTU를 처음 접했을 때는 지금껏 사용해 오던 프로토콜하고는 느낌이 달라서 왜 이렇게 생겨 먹은 것인 지부터 이해해야 했습니다.

보통 시리얼 통신은 장비에 맞추어 작성합니다. 장비에 어떤 입출력이 있고 무엇을 제어해야 하는지와 구해야 할 정보에 따라서 프로토콜을 설계합니다. 즉, 장비와 프로젝트에 따라서 프로토콜이 바뀌고 그때마다 프로그램을 수정하거나 새로 작성합니다.

[내용 추가] 모드버스는 함수에 따라 패킷 구성이 다릅니다. 함수마다 요구하는 내용이 다르기 때문이죠. 그래서 "모드버스 프로토콜 기본구성"이라는 제목에 공통으로 들어가는 내용으로 설명했는데, 댓글을 보니 모든 함수가 같은 패킷 구성으로 되어있는 것으로 오해하시는 것 같습니다. 저의 생각이 짧았네요. "모드버스 프로토콜 패킷 구성"으로 변경하고 함수 별로 구성이 다르다는 점을 언급하면서 참고 사이트를 추가하고 설명을 변경했습니다. 혼란을 드려 죄송합니다.

MODBUS Memory Map Table

그러나 MODBUS는 장비가 바뀌어도 프로토콜은 바뀌지 않습니다. 생소한 제품도 MODBUS를 지원한다면 프로토콜 수정 없이 통신이 가능한데, 이유는 사전에 정한 것이 있기 때문입니다. 바로 MODBUS Memory Map입니다.

모드버스 메모리맵 테이블 예제
MODBUS Memory Map Table

장비가 MODBUS를 지원한다면 반드시 위와 같은 형식의 메모리 맵을 갖추어야 합니다. 이전까지의 시리얼 통신은 장비와 직접 통신하는 느낌이라면 MODBUS 통신은 장비 사이에 메모리 맵이 가로막고 있는 것 같습니다. 장비는 자기 상태가 바뀌면 메모리 맵에 미리 지정된 주소의 값을 변경합니다. 외부 프로그램은 장비의 메모리 맵을 읽어서 상태를 확인하고 제어하고 싶다면 기능에 해당하는 주소의 값을 변경합니다. 장비는 계속해서 그 주소 값을 확인해서 시스템에 반영합니다.

모드버스 메모리맵 테이블 예제
MODBUS Memory Map Table 예제

MODBUS 통신을 지원하는 장비는 외부에 위와 같은 모드버스 메모리 맵 테이블을 제공합니다. Slot #1번 입력 값이 필요하다면 40001번 레지스터 값을 읽으면 됩니다. 스위치 SW #1이 켜진 것을 알고 싶다면 40006번 레지스터를 읽고 Off하고 싶다면 같은 40006번 레지스터에 제어 값을 쓰기 하면 됩니다.

이와 같이 장비의 기능에 따라서 메모리 맵을 구성 합니다. MODBUS 프로토콜은 단지 장비의 메모리 맵에서 특정 주소의 값을 읽거나 쓰기를 하는 것뿐입니다. 이런 이유로 장비에 따라서 메모리 맵 구성이 다를 뿐 MODBUS 프로토콜은 바뀌지 않습니다.

용도에 따른 코일과 레지스터 영역

모드버스 코일/레지스터
모드버스 코일/레지스터

MODBUS 메모리 맵을 다시 보면 번호 구역에 따라 코일과 레지스터로 나뉘어 있는데, 이는 MODBUS가 PLC를 대상으로 만들어졌기 때문입니다. 코일은 1 bit의 값과 같고 레지스터는 2byte의 word 값으로 생각할 수 있습니다. 즉, 코일은 On/Off하는 스위치를, 레지스터는 16bit의 입출력 값입니다.

코일과 레지스터에는 각각 읽기·쓰기가 가능한 영역과 읽을 수만있는 영역으로 나뉘며 해당 영역의 코일과 레지스터에 접근하려면 지정된 함수 코드를 사용해야 합니다.

MODBUS 프로토콜을 처음 접했을 때 구역별로 함수 번호가 따로 있는 이유가 의아했습니다. 어차피 읽고 쓰기 작업인데 레지스터 번호만 알면 되지 왜 읽기·쓰기 함수 번호를 코일과 레지스터 영역에 따라서 다르게 지정했을까 하는 것이었습니다. 즉, 읽기 하나·쓰기 하나 정해 놓고 주소를 지정하면 간단하지 않나 하는 것이죠.

그러나 MODBUS는 구역을 확실히 나누고 구역별로 특성을 지키기 위함인지 별도로 함수 번호를 만들었습니다. 그리고 4번 함수에 주소 값을 0을 지정하면 30001번 레지스터 값을 반환합니다. 즉, MODBUS는 레지스터 번호와는 별도로 주소 값을 같습니다.

조금 헷갈리죠? 간단히 말씀드리면 3번·6번·16번 함수는 레지스터 40001번부터 4999번까지 다룹니다. 이렇게 정해져 있기 때문에 40001번 레지스터 값을 읽는 다고 해서 3번 함수에 40001번 주소로 지정하지 않습니다. 3번 함수에 0번 주소로 지정합니다. 레지스터 30001번부터 39999번까지는 4번 함수를 사용하는데, 30001번 레지스터를 읽는다고 4번 함수 30001을 지정하는 것이 아니라 4번 함수 0번 주소로 지정합니다.

다시 말씀드려서 1~9999, 10001~19999, 30001~39999, 40001~49999 구역별로 함수가 지정되어 있어서 첫 번째 레지스터 번호의 주소는 모두 0번이 됩니다. 즉, 모든 함수는 주소의 시작이 0번부터 같아서 각 구역별로 지정한 함수는 다른 구역을 침범할 수 없습니다.

주의할 것은 코일과 레지스터 번호는 1번부터 시작하지만, 프로그래머는 0번부터 시작하는 습관이 있습니다. 그래서 번호는 1번부터 시작하지만, 주소는 0번부터 시작합니다. 그러나 주소도 1번부터 시작하는 장비가 있습니다. 그러므로 반드시 1번의 주소가 0이라고 판단하지 말고 장비에서 제공하는 모드버스 메모리 맵 테이블을 확인해야 합니다.

MODBUS 프로토콜 패킷 구성

MODBUS 프로토콜의 패킷 구성은 어떤 장치와 통신하는지 정하는 국번, 함수 번호 그리고 함수에 따른 필요 데이터로 구성됩니다. 즉, 함수 별로 패킷 구성이 다릅니다. 모든 함수에 대한 패킷 구성을 설명드리기에는 내용이 너무 많아서 4번 함수에 대해서만 예제로 설명 드리겠습니다. 이외의 각 함수별로 패킷 구성은 여기를 클릭하셔서 참고하여 주십시오.

모드버스 4번 함수 요청 패킷 구성
모드버스 4번 함수 요청 패킷 구성

위의 그림은 장치에게 MODBUS 4번 함수로 요청하는 패킷 구조이며 내용을 분석하면 아래와 같습니다.

  • 01: 슬레이브 1번에게(1번 장치에게)
  • 04 : 4번 함수를 이용하여 30001~39999사이에 있는 레지스터 중
  • 00 0A : 10번 주소부터
  • 00 01 : 1개 레지스터 값을 요구

정리하여 말씀드리면 1번 장치에게 10번 주소부터 1개의 레지스터 값을 요청한 것입니다. 10번 주소는 30001+10이므로 모드버스 메모리맵에서는 30011번이 됩니다. 30011번에서 1개의 레지스터를 요청했으므로 결국 30011번의 레지스터 값을 요청한 것입니다.

MODBUS 프로토콜 기본 구성
모드버스 4번 함수 응답 프로토콜

장치는 위의 패킷 구성으로 응답합니다. 예로 든 내용을 정리하면 이렇습니다.

  • 01: 국번 1번 슬레이브이고
  • 04: 4번 함수에 대한 응답
  • 02: 전송할 데이터 길이는 2 byte(1개의 레지스터 값을 요구했으므로)
  • 12 34 : 데이터 값

이와 같이 MODBUS 통신은 장비에 따라 프로토콜이 바뀌는 것이 아니라 MODBUS 메모리 맵이 장비에 따라 구성되는 것입니다. 그러므로 MODBUS 장비에 대해 몰라도 메모리 맵을 알고 있다면 MODBUS 통신으로 제어할 수 있고 상태 값을 구할 수 있습니다.

주의할 점 1) 모드버스의 패킷 설명을 보면 아리송한 부분이 몇 가지 있습니다. 첫 번째로 데이터와 CRC의 바이트 순서가 다릅니다. 이점을 꼭 유의하셔야 합니다. 레지스터 값은 2 바이트의 word 데이터입니다. 전송 바이트 순서를 따져야 하는데요, 모드버스에서는 기본이 빅엔디안입니다. 즉, 큰 자리 숫자부터 전송합니다.

빅엔디안 vs. 리틀엔디안

장치에서 전송할 데이터가 0x1234라면 0x12, 0x34 순서로 전송됩니다. 그러나 모드버스의 CRC는 리틀엔디안으로 보내야 합니다. CRC를 계산했더니 0xC812라면 0x12부터 먼저 보내고 0xC8을 전송해야 합니다. 왜 이렇게 하는지는 아직도 그 깊은 뜻을 찾지 못했습니다. 다만, 같은 프로토콜에서 바이트 전송 순서가 다르다는 점에 매우 유의해야 합니다.

주의할 점 2) 장치에 요청할 때는 데이터 길이가 1인데, 응답 데이터의 길이는 2입니다. 패킷 구성의 제목만 보면 오해할 수 있는데요, 내용까지 확인하면 이유는 타당합니다. 요청할 때는 레지스터의 개수이며 응답에서는 데이터의 길이입니다. 모드버스의 레지스터 값은 2바이트이므로 응답 데이터의 길이는 요청 레지스터 개수의 두 배가 됩니다.

어떻습니까? 이렇게 장비의 MODBUS 메모리 맵을 보니 어떻게 제어하고 상태를 읽을 수 있을지 감이 잡히시죠? 잘 만들어진 장비는 누구나 이해하기 쉽게 모드버스 메모리 맵 테이블을 제공합니다. 잘 정리된 메모리 맵 테이블이 있다면 구구하게 설명하는 수고를 줄일 수 있습니다.

MODBUS 프로토콜 메모리 맵과 함수 정리

이와 같이 MODBUS 프로토콜은 메모리 맵이 중요합니다. 장비 특성을 알고 메모리 맵에서 필요한 내용을 분석하면 이후로 통신 처리하기 편합니다.

MODBUS 메모리 맵과 함수
MODBUS 메모리 맵 vs. 함수

MODBUS의 블록별로 정리한 메모리 테이블입니다. 제일 많이 사용한 함수 번호는 3,4,6번이고 자주는 아니지만, 1, 5번을 사용했습니다.

libmodbus 추천

MODBUS-RTU 프로토콜은 단순해서 구현하기 어렵지 않지만, 그래도 주의할 내용이 많아서 직접 코딩하는 것보다는 libmodbus 라이브러리를 사용할 것을 권합니다. 매우 오랫동안 많은 개발자가 사용해서 신뢰할 수 있으며 온라인에 올라온 자료가 많아서 도움을 구하기 쉽습니다.

MODBUS-ASCII와 MODBUS-RTU 차이점

시리얼 통신에서 MODBUS는 두 가지 방식으로 구현할 수 있습니다. MODBUS-ASCII와 MODBUS-RTU인데요, MODBUS-ASCII는 이름에서 알 수 있듯이 개행 문자(0x0d 0x0a)를 섞은 아스키 통신입니다. 이에 비해 MODBUS-RTU는 바이너리 통신입니다.

MODBUS-ASCII는 타이핑만으로 제어할 수 있어서 만든 것 같은데, 단 한 번도 사용해 본 적이 없습니다. 구현해 달라는 요청을 받은 적도 없고요.

언급드린 내용 외에 MODBUS를 처음 접하면서 이해가 안 되었던 이상한 점을 정리해서 글을 올렸습니다. 아래 글 내용도 참고하세요.

모드버스 예외 상항 처리 방법

마스터로부터 모드버스 통신 패킷을 수신했는데 CRC 오류가 발생했거나, 처리 가능한 함수 요청이 아니거나, 지정한 주소의 레지스터가 없을 경우 어떻게 처리해야 할까요? 이런 내용을 담아서 모드버스 예외 상황 처리 방법을 "모드버스 통신 오류 시 응답 방법" 글에 정리하여 올렸습니다. 모드버스 프로토콜뿐만 아니라 rs485 통신에서 주의할 사항을 함께 다루었으므로 내용을 참고하세요.

모드버스 관련 글

SNS 공유하기
💬 댓글 개
최근글
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.