본문 바로가기

Software

Modbus RTU & libmodbus

728x90


modbus와 libmodbus 활용


Modbus RTU 프로토콜 구조


Modbus RTU는 RS-485 시리얼 통신을 활용하는 마스터-슬레이브 구조를 사용하며 

이 구조에서는 마스터는 슬레이브에 요청을 보내고 응답을 기다립니다

Modbus RTU는 1대 다통신이나 다대 다구조로 마스터에서 255개의 슬레이브와 통신 할 수 있습니다

일반적으로 센서나 액츄에이터 디바이스들을 슬레이브를 센서로 부터 데이터를 받는 디바이스는 마스터로 정의합니다


Modbus RTU 프로토콜의 구성단위는 PDU(Protocol Data Unit)로 불리우는 Function Code와 DATA 구성 단위와

PDU에 Additional address와 Error Check가 앞뒤로 붙어 ADU(Application Data Unit)으로 정의합니다



그리고 ADU에 앞뒤로 Start와 End에 ADU와 ADU 사이간 3.5 char 이상의 여분을 두게끔 한것이 RTU(Remote Terminal Unit)이 됩니다


여기서 Additional address는 슬레이브 ID로 장치의 고유한 주소(0~247)를 나타냅니다 이 주소는 제조사에 따라 임의로 정하고 있기에 장치를 특정하기 위해서는 제조사에서 발행하는 데이터시트를 참조하여 주소를 확인해야 합니다


Modbus 시리얼 통신은 RTU와 ASCII가 정의되어 있는데

ASCII는 Start와 End를 ":" (16진수 3A)로 표시하고 DATA처리를 ASCII형태로 처리한다는 차이점만 있으며 동일한 구조를 취합니다

이와 같이 Modbus RTU, ASCII에는 기능 코드(Function Code)란것이 존재하는데 이것은 요청의 목적을 정의하며 아래와 같습니다


Function Code

기능 코드를 사용하여 슬레이브 장치 메모리(Coil, Register)에 값을 읽거나 쓰는 사전 정의된 기능을 사용할 수 있습니다


기능 코드는 대표적으로 코일(Coil)과 레지스터(Register)란 종류로 구분되는데 

코일은 On/Off 상태만 저장하는 구조이고

레지스터는 숫자값을 저장하는 구조라고 간단하게 생각하시면 쉽습니다


Modbus의 데이터 모델은 아래와 같이 크게 4가지 경우수로 볼 수 있습니다

아래에서는 각 기능 코드에 대한 설명과 일반적으로 읽기와 쓰기에 있어 사용빈도가 높은 기능코드에 대해 자세히 다뤄보고자 합니다


0x01 : Read Coil Status

출력 값(On/Off)을 읽는 기능  (시작 번지로 부터 요청 갯수만큼 응답)


0x02 : Read Input Status

입력 상태 값(On/Off)을 읽는 기능 (시작 번지로 부터 요청 갯수만큼 응답)


0x03 : Read Holding Registers

출력 데이터 값을 읽는 기능 (시작 번지로 부터 요청한 갯수만큼 응답)


0x04 : Read Input Registers

입력 상태 값을 읽는 기능 (시작 번지로 부터 요청한 갯수만큼 응답)


0x05 : Force Single Coil

단일 코일에 입력 값 (On/Off)을 쓰는 기능


0x06 : Preset Single Register

단일 레지스터에 입력 값을 쓰는 기능


0x15 : Force Multiple Coils

다수의 코일에 입력 값(On/Off)을 쓰는 기능


0x16 : Preset Mutiple Registers

다수의 레지스터에 입력 값을 쓰는 기능



libmodbus를 사용한 Modbus RTU 예시 코드


장치를 다음과 같이 구축합니다

위 이미지에서 사용된 USB to RS485 통신모듈은 연결시 디바이스 접근 경로는 /dev/ttyUSB0 였으며 이는 사용자 환경에 따라 상이할 수 있습니다 (#>dmesg 명령으로 확인 필요합니다)


우분투 환경에서 아래와 같이 libmodbus 라이브러리를 설치합니다

#>sudo apt-get install libmodbus-dev


아래 예시는 0x03기능코드를 사용하여 센서디바이스로 부터 온습도를 읽고 0x06 기능코드를 사용하여 센서 디바이스 슬레이브 주소를 변경후 변경된 슬레이브 주소로 다시 연결한뒤 0x04 기능코드를 사용하여 설정한 슬레이브주소를 읽기를 시도 해볼 수 있습니다


아래 파일을 modbus_rtu_test.c로 작성후 #>gcc -o modbus_rtu_test modbus_rtu_test.c -lmodbus 로 빌드하여 볼 수 있습니다

실행은 #>sudo modbus_rtu_test 로 관리자 권한으로 수행합니다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus/modbus-rtu.h>
 
int main()
{
        modbus_t *ctx;
 
        // libmodbus rtu 컨텍스트 생성
        ctx = modbus_new_rtu("/dev/ttyUSB0"9600'E'81);
        /*
         * 디바이스 : /dev/ttyXXX
         * 전송속도 : 9600, 19200, 57600, 115200
         * 패리티모드 : N (none) / E (even) / O (odd)
         * 데이터 비트 : 5,6,7 or 8
         * 스톱 비트 : 1, 2
        */
        if (ctx == NULL) {
                fprintf(stderr, "Unable to create the libmodbus context\n");
                return -1;
        }
 
        modbus_set_slave(ctx, 242); // 슬레이브 주소
        modbus_set_debug(ctx, TRUE); // 디버깅 활성
 
        // 연결
        if (modbus_connect(ctx) == -1) {
                fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
                modbus_free(ctx);
                return -1;
        }
 
        uint16_t *read_registers;
        read_registers = (uint16_t *malloc(256 * sizeof(uint16_t));
 
        // 0x03 기능 코드 (read holding register)
        // 0x012C : 주소 / 2 : 읽을 갯수 (예시)
        modbus_read_registers(ctx, 0x012C2, read_registers);
 
        // print temperature / humidity
        printf("%d.%d C\n",  read_registers[0]/100, read_registers[0]%100); // 2545 => 25.45 C
        printf("%d.%d %%\n", read_registers[1]/100, read_registers[1]%100); // 5319 => 53.19 %
 
        // 0x06 기능 코드 (Preset Single Register)
        // 0x0000 : 주소(슬레이브 주소 변경) / 245 : 기존 242에서 245로 수정
        modbus_write_register(ctx, 0x0000245);
 
        // 변경된 슬레이브 주소로 재연결
        modbus_close(ctx);
        modbus_set_slave(ctx, 245);
        if (modbus_connect(ctx) == -1) {
                fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
                modbus_free(ctx);
                return -1;
        }
 
        // 0x04 기능 코드 (Read Input Registers)
        // 설정된 슬레이브 주소값을 읽기
        modbus_read_input_registers(ctx, 0x00001, read_registers);
        printf("Slave Address : %d\n", read_registers[0]);
 
        modbus_close(ctx);
        modbus_free(ctx);
        return 0;
}
 
cs




728x90