Modbus RTU 개발 예

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

Modbus RTU 개발 예

本記事在 Linux 上基于 libmodbus 库进行 Modbus RTU 开发,在进行实验之前需要先インストール libmodbus 库,以及 socat 工具実装バーチャルシリアルポート。

准备工作

インストール libmodbus

可以按 libmodbus 软件库 中「インストール」一节的步骤进行手动编译インストール。

インストール socat

sudo apt install socat

socat 是一个功能强大的网络工具,本实验将借助它来実装2つ相互连接的バーチャルシリアルポート,因此必要なし。准备真实的シリアルデバイス。について socat 的详细用法,可以参照 socat Command

代码実装

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

Modbus RTU slave

#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      // Number of cycles
#define Server_ID       17      // 从端デバイスアドレス
#define ADDRESS_START    0      // 测试レジスタ開始アドレス
#define ADDRESS_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, "Error 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 Closed\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 master

#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        // Number of cycles
#define Server_ID       17        // 从端デバイスアドレス
#define ADDRESS_START    0        // 测试レジスタ開始アドレス
#define ADDRESS_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 = ADDRESS_END - ADDRESS_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 = ADDRESS_START; addr < ADDRESS_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 = ADDRESS_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("Address = %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("address = %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("Address = %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("address = %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("Address = %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("Address = %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("Address = %d\n", addr);
                    nb_fail++;
                }
                else {
                    // 読み取り后进行比较
                    if (tab_rq_registers[0] != tab_rp_registers[0]) {
                        printf("ERROR modbus_read_registers (%d)\n", rc);
                        printf("Address = %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("Address = %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("Address = %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("Address = %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++;
                        }
                    }
                }
            }

            // function code 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("Address = %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("Address = %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("Address = %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 마스터 및 슬 레이 브 의 데이터 통신 프로세 스를 볼 수 있습니다 .

Related Tags
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.