Modbus RTU 개발 예

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

Modbus RTU 개발 예

本文在 Linux 上基于 libmodbus 库进行 Modbus RTU 개발,在进行实验之前需要先安装 libmodbus 库,以及 socat 도구实现虚拟시리얼 포트。

准备工作

安装 libmodbus

可以按 libmodbus 软件库 中「安装」一节的步骤进行手动编译安装。

安装 socat

sudo apt install socat

socat 是一个강력한 성능的네트워크도구,本实验将借助它来实现两个相互연결的虚拟시리얼 포트,因此不需要准备真实的시리얼 포트设备。关于 socat 的详细用法,可以参考 socat 命令。

代码实现

完整代码可从 https://github.com/getiot/linux-c/tree/main/library/libmodbus 获取。

Modbus RTU 슬레이브

#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include "modbus.h" // 引用libmodbus库

#define Loop             1      // 반복 횟수
#define Server_ID       17      // 从端设备주소
#define 주소_START    0      // 测试레지스터시작주소
#define 주소_END     99      // 测试레지스터종료주소

int main(int argc, char *argv[])
{
    modbus_t *ctx = NULL;
    int nb = 0; // 需要测试的레지스터个数
    uint8_t* tab_rq_bits;       // 用于저장보내기或수신的데이터(下同)
    uint8_t* tab_rp_bits;
    uint16_t *tab_rq_registers;
    uint16_t *tab_rw_rq_registers;
    uint16_t *tab_rp_registers;
    char *port;
    modbus_mapping_t *mb_mapping;

    if (argc == 2) {
        port = argv[1];
    } else {
        port = "COM5";
    }

    // 创建一个RTU유형的容器
    ctx = modbus_new_rtu(port, 19200, 'N', 8, 1);
    // 设置从端주소
    modbus_set_slave(ctx, Server_ID);
    // 设置debug모드
    modbus_set_debug(ctx, true);
    // RTU 모드下表示시리얼 포트 열기
    if (modbus_connect(ctx) == -1)
    {
        fprintf(stderr, "Connection failed: %s \n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 申请4块内存区用以存放레지스터데이터,这里申请500个레지스터주소
    mb_mapping = modbus_mapping_new(500, 500, 500, 500);
    if (mb_mapping == NULL) {
        fprintf(stderr, "오답 mapping: %s \n",modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 循环수신查询帧并回复消息
    for (;;) {
        uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
        int rc = 0;
        rc = modbus_receive(ctx, query);//获取查询프레임

        if (rc >= 0) {
            // rc is the qury size
            modbus_reply(ctx, query, rc, mb_mapping); // 回复响应프레임
        }
        else {
            // connection closed by the client or error
            printf("Connection 닫기d\n");
        }
    }
    printf("Quit the loop : %s \n", modbus_strerror(errno));

    // 释放内存
    modbus_mapping_free(mb_mapping);
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

Modbus RTU 마스터

#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include "modbus.h" // 引用libmodbus库

#define Loop             1        // 반복 횟수
#define Server_ID       17        // 从端设备주소
#define 주소_START    0        // 测试레지스터시작주소
#define 주소_END     99        // 测试레지스터종료주소

int main(int argc, char *argv[])
{
    // printf("%c", 0b01000001); // A
    modbus_t *ctx = NULL;

    int nb = 0; // 需要测试的레지스터个数

    uint8_t* tab_rq_bits;       // 用于저장보내기或수신的데이터(下同)
    uint8_t* tab_rp_bits;

    uint16_t *tab_rq_registers;
    uint16_t *tab_rw_rq_registers;
    uint16_t *tab_rp_registers;
    char *port;

    if (argc == 2) {
        port = argv[1];
    } else {
        port = "COM4";
    }

    // 创建一个RTU유형的容器
    ctx = modbus_new_rtu(port, 19200, 'N', 8, 1);
    // 设置从端주소
    modbus_set_slave(ctx, Server_ID);
    // 设置debug모드
    modbus_set_debug(ctx, true);
    // RTU 모드下表示시리얼 포트 열기
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s \n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    printf("Connection successed\n");

    /*Allocate and initialize the different memory space */
    // 계산需要测试的레지스터个数
    nb = 주소_END - 주소_START;

    // 以下申请内存块,泳衣저장보내기和수신各데이터
    tab_rq_bits = (uint8_t*)malloc(nb * sizeof(uint8_t));
    memset(tab_rq_bits, 0, nb*sizeof(uint8_t));

    tab_rp_bits = (uint8_t*)malloc(nb * sizeof(uint8_t));
    memset(tab_rp_bits, 0, nb*sizeof(uint8_t));

    tab_rq_registers = (uint16_t*)malloc(nb * sizeof(uint16_t));
    memset(tab_rq_registers, 0, nb*sizeof(uint16_t));

    tab_rw_rq_registers = (uint16_t*)malloc(nb * sizeof(uint16_t));
    memset(tab_rw_rq_registers, 0, nb*sizeof(uint16_t));

    tab_rp_registers = (uint16_t*)malloc(nb * sizeof(uint16_t));
    memset(tab_rp_registers, 0, nb*sizeof(uint16_t));

    int nb_loop = 0, nb_fail = 0, addr = 0, rc = 0;

    while (nb_loop++ < Loop) {
        // 从시작주소开始顺序测试
        for (addr = 주소_START; addr < 주소_END; addr++) {
            sleep(1);
            int i = 0;

            // 生成随机数用于测试
            for (i = 0; i < nb; i++) {
                tab_rq_registers[i] = (uint16_t)(65535.0 * rand() / (RAND_MAX + 1.0));
                tab_rw_rq_registers[i] = ~tab_rq_registers[i];
                tab_rq_bits[i] = tab_rq_registers[i] % 2;
            }
            nb = 주소_END - addr;

            // 测试코일레지스터的单个读写
            printf("modbus_write_bit()...\n");
            rc = modbus_write_bit(ctx, addr, tab_rq_bits[0]); // 写코일레지스터
            if (rc != 1) {
                printf("ERROR modbus_write_bit (%d)\n", rc);
                printf("주소 = %d,value = %d \n", addr, tab_rq_bits[0]);
                nb_fail++;
            }
            else {
                // 쓰기之后,再읽기并比较
                rc = modbus_read_bits(ctx, addr, 1, tab_rp_bits);
                if (rc != 1 || tab_rq_bits[0] != tab_rp_bits[0]) {
                    printf("ERROR modbus_read_bits single(%d)\n", rc);
                    printf("주소 = %d\n", addr);
                    nb_fail++;
                }
            }

            // 测试코일레지스터的批量读写
            printf("modbus_write_bits()...\n");
            rc = modbus_write_bits(ctx, addr, nb, tab_rq_bits);
            if (rc != nb) {
                printf("ERROR modbus_write_bits (%d)\n", rc);
                printf("주소 = %d,nb = %d\n", addr, nb);
                nb_fail++;
            }
            else {
                // 쓰기之后,再읽기并比较
                rc = modbus_read_bits(ctx, addr, nb, tab_rp_bits);
                if (rc != nb) {
                    printf("ERROR modbus_read_bits \n");
                    printf("주소 = %d,nb = %d\n", addr, nb);
                    nb_fail++;
                }
                else {
                    // 进行比较
                    for (i = 0; i < nb; i++) {
                        if (tab_rp_bits[i] != tab_rq_bits[i]) {
                            printf("ERROR modbus_read_bits (%d)\n", rc);
                            printf("주소 = %d, Val = %d(0x%x) != %d (0x%x)\n",
                                addr, tab_rq_bits[i], tab_rq_bits[i],
                                tab_rp_bits[i], tab_rp_bits[i]);
                            nb_fail++;
                        }
                    }
                }
            }

            // 测试유지 레지스터的单个读写
            printf("modbus_write_register()...\n");
            rc = modbus_write_register(ctx, addr, tab_rq_registers[0]);
            if (rc != 1) {
                printf("ERROR modbus_read_bits (%d)\n", rc);
                printf("주소 = %d, Val = %d(0x%x)\n",
                    addr, tab_rq_registers[0], tab_rq_registers[0]);
                nb_fail++;
            }
            else {
                // 쓰기之后进行읽기
                rc = modbus_read_registers(ctx, addr, 1, tab_rp_registers);
                if (rc != 1) {
                    printf("ERROR modbus_read_registers (%d)\n", rc);
                    printf("주소 = %d\n", addr);
                    nb_fail++;
                }
                else {
                    // 읽기后进行比较
                    if (tab_rq_registers[0] != tab_rp_registers[0]) {
                        printf("ERROR modbus_read_registers (%d)\n", rc);
                        printf("주소 = %d, Val = %d(0x%x) != %d (0x%x)\n",
                            addr, tab_rq_registers[0], tab_rq_registers[0],
                            tab_rp_registers[0], tab_rp_registers[0]);
                        nb_fail++;
                    }
                }
            }

            // 测试코일레지스터的批量读写
            printf("modbus_write_registers()...\n");
            rc = modbus_write_registers(ctx, addr, nb, tab_rq_registers);
            if (rc != nb) {
                printf("ERROR modbus_write_bits (%d)\n", rc);
                printf("주소 = %d, nb = %d \n", addr, nb);
                nb_fail++;
            }
            else {
                // 进行읽기测试
                rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers);
                if (rc != nb) {
                    printf("ERROR modbus_read_registers (%d)\n", rc);
                    printf("주소 = %d\n", addr);
                    nb_fail++;
                }
                else {
                    for (i = 0; i < nb; i++) {
                        // 읽기后进行比较
                        if (tab_rq_registers[i] != tab_rp_registers[i]) {
                            printf("ERROR modbus_read_registers (%d)\n", rc);
                            printf("주소 = %d, Val = %d(0x%x) != %d (0x%x)\n",
                                addr, tab_rq_registers[0], tab_rq_registers[0],
                                tab_rp_registers[0], tab_rp_registers[0]);
                            nb_fail++;
                        }
                    }
                }
            }

            // 기능 코드 23 (0x17) 读写多个레지스터的测试
            printf("modbus_write_and_read_registers()...\n");
            rc = modbus_write_and_read_registers(ctx,
                addr, nb, tab_rw_rq_registers,
                addr, nb, tab_rp_registers);

            if (rc != nb) {
                printf("ERROR modbus_read_ad_write_registers (%d)\n", rc);
                printf("주소 = %d,nb = %d\n", addr, nb);
                nb_fail++;
            }
            else {
                // 읽기并比较
                for (i = 0; i < nb; i++) {
                    if (tab_rp_registers[i] < tab_rw_rq_registers[i]) {
                        printf("ERROR modbus_read_and_write_registers READ\n");
                        printf("주소 = %d,value %d (0x%X) != %d (0x%X)\n",
                            addr, tab_rp_registers[i], tab_rw_rq_registers[i],
                            tab_rp_registers[i, tab_rw_rq_registers[i]]);
                        nb_fail++;
                    }
                }

                rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers);
                if (rc != nb) {
                    printf("ERROR modbus_read_registers (%d) \n", rc);
                    printf("주소 = %d,nb = %d\n", addr, nb);
                }
                else {
                    for (i = 0; i < nb; i++) {
                        if (tab_rw_rq_registers[i] != tab_rp_registers[i) {
                            print f (" ER R OR mod bus _ read _ and _ write _ register s \ n ");
                            print f ( " Add ress = % d , value % d (0 x % X) ! = % d (0 x % X) \ n ",
                                addr, tab_rw_rq_registers
                                tab_rp_registers
                            nb _ fail ++ ;
                        }
                    }
                }
            }
        }

        print f ( " Test : ");
        if (nb _ fail) {
            print f (" % d FA IL S \ n ", nb _ fail);
        } else {
            print f ( " SU CC ESS \ n ");
        }
    }

    기억 을 자유롭게 해 제 하라
    free (tab _ ... _ bits)
    free (tab _ r p _ bits);
    free (tab _ ... _ register s)
    free (tab _ r p _ register s);
    free (tab _ r w _ ... register s)

    / / 연결 을 닫 습니다 .
    mod bus _ close (ct x);
    mod bus _ free (ct x)

    0 을 반환 합니다 ;
}

메 이크 업 파 일

CC = g cc
CF LA GS = - I / us r / local / include / mod bus / - l mod bus - g

MAS TER = mod bus - r tu - master
모델 번호 : mod bus - r tu - slave

모두 :
        (CC)- o (M AS TER) (M AS TER). c (CF LA GS)
        (CC)- o (S LA VE) (S LA VE). c (CF LA GS)

청소 :
        r m - r f (마 스터) (슬 레이 브)

测试

가상 직 렬 포 트 시작

$so cat - d - d PT Y PT Y
2021/07/16 14:36:43 socat
2021/07/16 14:36:43 socat
2021/07/16 14:36:43 socat

슬 레이 브 디 바 이스 시작 , 마스터 명령 대기

./ mod bus - r tu - slave / dev / p ts / 7

마스터 스테 이션 장 비를 가 동 하고 차례 로 슬 레이 브 스테 이션 에 명령을 내 립니다 .

./ mod bus - r tu - master / dev / p ts / 8

이제 터 미 널 에서 Mod bus R TU 마스터 및 슬 레이 브 의 데이터 통신 프로세 스를 볼 수 있습니다 .

相关标签
실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注