Modbus RTU 开发示例

freefree技术Resources

这篇内容可直接阅读,适合用于基础学习和Search引流。

Modbus RTU 开发示例缩略图
🌐 This page is not yet available in English. Showing the Chinese version. Back to Chinese page.
本文目录
  1. 1. 准备工作
  2. 2. 安装 libmodbus
  3. 3. 安装 socat
  4. 4. 代码实现
  5. 5. Modbus RTU 从站
  6. 6. Modbus RTU 主站
  7. 7. Makefile
  8. 8. 测试

本文在 Linux 上基于 libmodbus 库进行 Modbus RTU 开发,在进行实验之前需要先安装 libmodbus 库,以及 socat Tools实现虚拟串口。

准备工作

安装 libmodbus

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

安装 socat

sudo apt install socat

socat 是一个Function强大的网络Tools,本实验将借助它来实现两个相互connect的虚拟串口,因此不需要准备真实的串口设备。关于 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      // Number of cycles
#define Server_ID       17      // 从端Device address
#define ADDRESS_START    0      // 测试registerstarting address
#define ADDRESS_END     99      // 测试registerEndaddress

int main(int argc, char *argv[])
{
    modbus_t *ctx = NULL;
    int nb = 0; // 需要测试的register个数
    uint8_t* tab_rq_bits;       // 用于保存send或receive的data(下同)
    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";
    }

    // 创建一个RTUType的容器
    ctx = modbus_new_rtu(port, 19200, 'N', 8, 1);
    // 设置从端address
    modbus_set_slave(ctx, Server_ID);
    // 设置debug模式
    modbus_set_debug(ctx, true);
    // RTU 模式下表示Open the serial port
    if (modbus_connect(ctx) == -1)
    {
        fprintf(stderr, "Connection failed: %s \n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 申请4块内存区用以存放register data,这里申请500个register address
    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;
    }

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

        if (rc >= 0) {
            // rc is the qury size
            modbus_reply(ctx, query, rc, mb_mapping); // 回复Responsemessage
        }
        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 主站

#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        // 从端Device address
#define ADDRESS_START    0        // 测试registerstarting address
#define ADDRESS_END     99        // 测试registerEndaddress

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

    int nb = 0; // 需要测试的register个数

    uint8_t* tab_rq_bits;       // 用于保存send或receive的data(下同)
    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";
    }

    // 创建一个RTUType的容器
    ctx = modbus_new_rtu(port, 19200, 'N', 8, 1);
    // 设置从端address
    modbus_set_slave(ctx, Server_ID);
    // 设置debug模式
    modbus_set_debug(ctx, true);
    // RTU 模式下表示Open the serial port
    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 */
    // 计算需要测试的register个数
    nb = ADDRESS_END - ADDRESS_START;

    // 以下申请内存块,泳衣保存send和receive各data
    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) {
        // 从starting address开始顺序测试
        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;

            // 测试线圈register的单个读写
            printf("modbus_write_bit()...\n");
            rc = modbus_write_bit(ctx, addr, tab_rq_bits[0]); // 写线圈register
            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++;
                }
            }

            // 测试线圈register的批量读写
            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++;
                        }
                    }
                }
            }

            // 测试保持register的单个读写
            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++;
                    }
                }
            }

            // 测试线圈register的批量读写
            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) 读写多个register的测试
            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]) {
                            printf("ERROR modbus_read_and_write_registers \n");
                            printf("Address = %d,value %d (0x%X) != %d (0x%X)\n",
                                addr, tab_rw_rq_registers[i], tab_rw_rq_registers[i],
                                tab_rp_registers[i], tab_rp_registers[i]);
                            nb_fail++;
                        }
                    }
                }
            }
        }

        printf("Test: ");
        if (nb_fail) {
            printf("%d FAILS\n",nb_fail);
        } else {
            printf("SUCCESS\n");
        }
    }

    // FREE the memory
    free(tab_rq_bits);
    free(tab_rp_bits);
    free(tab_rq_registers);
    free(tab_rp_registers);
    free(tab_rw_rq_registers);

    // close the connection
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

Makefile

CC=gcc
CFLAGS=-I/usr/local/include/modbus/ -lmodbus -g

MASTER=modbus-rtu-master
SLAVE=modbus-rtu-slave

all:
        (CC) -o(MASTER) (MASTER).c(CFLAGS)
        (CC) -o(SLAVE) (SLAVE).c(CFLAGS)

clean:
        rm -rf (MASTER)(SLAVE)

测试

启动虚拟串口

$ socat  -d  -d  PTY  PTY
2021/07/16 14:36:43 socat[23932] N PTY is /dev/pts/7
2021/07/16 14:36:43 socat[23932] N PTY is /dev/pts/8
2021/07/16 14:36:43 socat[23932] N starting data transfer loop with FDs [5,5] and [7,7]

启动从站设备,等待主站指令

./modbus-rtu-slave /dev/pts/7

启动主站设备,依次给从站下发指令

./modbus-rtu-master /dev/pts/8

现在,你可以从终端看到 Modbus RTU 主站和从站的data通信过程。

技术术语(共 6 个)—— 点击展开
Modbus RTU基于串行链路的Modbus协议,使用binaryCoding和CRC check
function codeModbusfunction code指定读/写OperationType,如01读线圈、03读保持register
registerModbus register存储data单元,分线圈/离散输入/保持/输入register四类
串口计算机与外部设备进行串行通信的物理接口
线圈Modbus位可读写data,address从00001开始
保持registerModbus 16位可读写data,address从40001开始
来源/Tools信息 —— 点击展开
来源 Modbus Chinese Network(modbus.cn) —— China leadingModbus通信协议技术社区 Category Modbus protocol标准 字数 7892 字 · 阅读约 20 分钟 更新 2022-11-18 永久链接 https://www.modbus.cn/11114.html
Recommended Tool: Modbus Debug Assistant WeChat Mini Program
Modbus Chinese Network官方推出的Modbus DebuggingTools,支持 Modbus RTU/TCP 实时通信debug、register读写、线圈控制、data监控和message分析。 无需安装,微信Search「Modbus DebuggingAssistant」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
RelatedTags
把这篇Resources用于真实项目?

进入Tools中心进行messageParse、CRC verification和设备debug,或Submit requirements获取选型与接入建议。

工程师member

把这篇文章变成可执行的debugResources

开通后可使用高级messageParse、Resources包Download、Code Example、工程案例和优先Technical Support,适合真实项目交付。

高级Tools不限次
Resources包与代码包
Complete Engineering Case Library
优先Technical Support入口

发表回复

您的邮箱address不会被公开。 必填项已用 * 标注