libmodbus学习(三)之Modbus协议转换

一、RTU Slaver–TCP Master

1.测试平台搭建

Modbus RTU转 Modbus TCP测试平台搭建如下,网关起协议转换作用。首先,master(主站)通过modbus tcp协议发送读取数据请求,网关接收后解析Modbus TCP请求后,将Modbus TCP协议转成Modbus RTU向slaver(从站)设备发送Modbus RTU请求。其次,slaver(从站)设备收到请求后进行Modbus RTU响应,网关接收到Modbus RTU响应后将其转换成Modbus TCP响应发给Modbus TCP。


image.png

MThings模拟从站设备,通过RS485串口通信与网关(开发板)进行通信

1.1Modbus RTU 的建立

在Modbus RTU通信中, PC机创建slaver,网关为master

image.png

slave上创建的寄存器种类、数量及地址如下,网关作为master通过Modbus RTU协议读取或写入寄存器的数值。
image.png

1.2Modbus TCP 的建立

在Modbus TCP通信中, PC机创建master,网关为slaver

image.png

master上可以进行读取和写入指定寄存器的操作,slave通过Modbus TCP协议响应这些操作。
image.png

1.3协议转换

网关将Modbus TCP-master的Modbus TCP请求数据转换成Modbus RTU请求数据给Modbus RTU-slaver

image.png

网关将Modbus RTU-slaver的Modbus RTU响应数据转换成Modbus TCP响应数据给Modbus TCP-master

image.png

1.4测试结果

image.png
image.png
image.png
[2020-12-08 18:55:32-128]NET000-发送:[00 03 00 00 00 06] 01 01 00 64 00 01

TCP Master发送线圈状态读取报文中括号中[00 03 00 00 00 06]是modbus-tcp的表头信息,其中
00 03代表包序号为3,
00 00代表协议是tcp,
00 06代表后面还有6byte有效数据。
后续数据为modbus协议的PDU(数据单元),其中
01为 Modbus-tcp协议的目标slaver ID(从站ID,实际设备是网关),
01为功能码,表示读取线圈状态,
00 64为线圈的地址100
00 01为数量1
PDU的含义:读取slaver ID为1的设备,地址为100的1个线圈状态。

网关收到请求后,将PUD截取,加上CRC校验转换成Modbus-RTU协议发起请求

Modbus-RTU-slaver接收到的请求如下

[2020-12-08 18:55:32-148]COM34-接收:01 01 00 64 00 01 bc 15

PDU不变,最后多了bc 15的CRC校验码。

Modbus-RTU-slaver响应如下

[2020-12-08 18:55:32-158]COM34-发送:01 01 01 01 90 48

01为 Modbus-RTU协议的目标slaver ID(从站ID,实际设备是PC模拟的设备)
01代表功能码,表示读取线圈状态,
01代表后面还有1byte有效数据
01代表读取数值
90 48是CRC校验
响应信息的含义:读取的线圈状态为1。

Modbus-RTU-master(实际设备为网关)接收到Modbus-RTU-slaver的响应后,截取PDU,加上Modbus-TCP表头,响应Modbus-TCP-master的请求,响应消息含义:读取的线圈状态为1。

[2020-12-08 18:55:32-208]NET000-接收:[00 03 00 00 00 04] 01 01 01 01

1.5测试程序

RTU Slaver–TCP Master

#include 
#include   
#include 
#include 
#include 
#include 
#include "unit-test.h"

int main(void)
{
    int socket;
    int rc;
    int i;
    modbus_t *tcp_ctx;
    modbus_t *rtu_ctx;

    modbus_mapping_t *mb_mapping;

    int header_length;
    uint8_t *query;
    uint16_t* regs_value = (uint16_t*)malloc(256*sizeof(uint16_t));
    int addr,nb,coil_statu;
    int coils,input_coils,regs,input_regs;

    uint16_t reg_value;

    /* 创建tcp server 对象、rtu master 对象*/
    tcp_ctx = modbus_new_tcp("192.168.1.184", 502);         //开发板ip
    rtu_ctx = modbus_new_rtu("/dev/ttymxc2",9600,'N',8,1);  //开发板串口

    /* 分配报文接收空间 */
    query = (uint8_t *)malloc(MODBUS_TCP_MAX_ADU_LENGTH);

    header_length = modbus_get_header_length(tcp_ctx);

    modbus_set_debug(tcp_ctx, TRUE);

    mb_mapping = modbus_mapping_new_start_address(
        UT_BITS_ADDRESS, UT_BITS_NB,
        UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB,
        UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX,
        UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB);
    
              
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n",
                modbus_strerror(errno));
        goto __error;
    }


    /* map 地址与存储空间 */
    mb_mapping->tab_bits = UT_BITS_TAB;
    mb_mapping->tab_input_bits = UT_INPUT_BITS_TAB;
    mb_mapping->tab_registers = UT_REGISTERS_TAB;
    mb_mapping->tab_input_registers = UT_INPUT_REGISTERS_TAB;

    /* 建立rtu连接 */
    modbus_set_slave(rtu_ctx,1);
    int nRet = modbus_connect(rtu_ctx);
    if (-1 == nRet)
    {
        printf("connect failed:%s\n", modbus_strerror(errno));
        goto __error;
    }
    
    /* 开启监听等待客户的建立连接 */
    socket = modbus_tcp_listen(tcp_ctx, 1);
    if (socket == -1)
    {
        printf("Unable to listen TCP!!%s\n",modbus_strerror(errno));
        modbus_free(tcp_ctx);
        goto __error;
    } 
    modbus_tcp_accept(tcp_ctx, &socket);


    for (;;) {

        do {
            rc = modbus_receive(tcp_ctx,query);
            } while (rc == 0);
        if (rc != -1) {
           /* 处理接收数据 解析功能码 */
            switch(query[header_length]){
                /* 功能码 */                            
                case 0x01: 
                            printf("[功能码:%d]读线圈状态\n",query[header_length]);
                            /* 获取地址 */
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);
                            printf("addr = %d nb = %d\n",addr,nb);
                            /* 向rtu从站读取线圈状态 */
                            modbus_read_bits(rtu_ctx,addr,nb,UT_BITS_TAB+addr-UT_BITS_ADDRESS); //读取线圈状态到指定table
                            printf("UT_BITS_TAB: %d mb_mapping->tab_bits:%d\n",UT_BITS_TAB[0],mb_mapping->tab_bits[0]);
                            break;                   
            
                case 0x02: 
                            printf("[功能码:%d]读离散输入\n",query[header_length]);
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);
                            printf("addr = %d nb = %d\n",addr,nb);
                            /* 向rtu从站读取输入离散 */
                            modbus_read_input_bits(rtu_ctx,addr,nb,UT_INPUT_BITS_TAB+addr-UT_INPUT_BITS_ADDRESS);   //读取输入离散到指定table
                            break;           
            
                case 0x03: 
                            printf("[功能码:%d]读保持寄存器\n",query[header_length]);
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);
                            printf("addr = %d nb = %d\n",addr,nb);
                            /* 向rtu从站读取保持寄存器 */                            
                            modbus_read_registers(rtu_ctx,addr,nb,UT_REGISTERS_TAB+addr-UT_REGISTERS_ADDRESS);  //读取保持寄存器到指定table
                            printf("UT_BITS_TAB: %d mb_mapping->tab_bits:%d\n",UT_REGISTERS_TAB[0],mb_mapping->tab_registers[0]);
                            break;
                
                case 0x04: 
                            printf("[功能码:%d]读输入寄存器\n",query[header_length]);
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);
                            printf("addr = %d nb = %d\n",addr,nb);
                            /* 向rtu从站读取输入寄存器 */                            
                            modbus_read_input_registers(rtu_ctx,addr,nb,UT_INPUT_REGISTERS_TAB+addr-UT_INPUT_REGISTERS_ADDRESS); //读取输入寄存器到指定table
                            break;
                
                case 0x05: 
                            printf("[功能码:%d]写单个线圈\n",query[header_length]);
                            /* 解析将写入的线圈状态 */
                            coil_statu = (query[header_length+3] == 0xFF)? 1: 0;
                            /* 向rtu从站写线圈状态 */
                            modbus_write_bit(rtu_ctx,addr,coil_statu);
                            break;
                
                case 0x06: 
                            printf("[功能码:%d]写单个保持寄存器\n",query[header_length]);
                            /* 获取地址 */
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 解析将写入的寄存器值 */ 
                            reg_value = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);
                            /* 向rtu从站写保持寄存器 */                           
                            modbus_write_register(rtu_ctx,addr,(const uint16_t)reg_value);
                            break;                                                        
                case 0x0F: 
                            printf("[功能码:%d]写多个线圈\n",query[header_length]);
                            /* 获取地址 */
                            //addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            //nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3); 
                            //modbus_write_bits(rtu_ctx,addr,nb);
                            break;
                case 0x10: 
                            printf("[功能码:%d]写多个保持寄存器\n",query[header_length]);
                            /* 获取地址 */
                            addr = MODBUS_GET_INT16_FROM_INT8(query,header_length+1);
                            /* 获取数量 */
                            nb = MODBUS_GET_INT16_FROM_INT8(query,header_length+3);

                            /* 解析将写入的寄存器值 */ 
                            for(i = 0;i < nb;i++)
                            {
                                regs_value[i] = MODBUS_GET_INT16_FROM_INT8(query,header_length+6+2*i);
                            }
                            printf("regs_value: %d\n", regs_value[0]);
                            printf("regs_value: %d\n", regs_value[1]);
                            printf("addr: %d nb: %d\n", addr, nb);
                            /* 向rtu从站写保持寄存器 */                              
                            modbus_write_registers(rtu_ctx,addr,nb,(const uint16_t* )regs_value);
                            break;  
                default:    
                            break;
            }

            /* 响应接收 */
            modbus_reply(tcp_ctx, query, rc, mb_mapping);
        } else {
            /* 连接关闭或者出错 */
            break;
        }
    }

    printf("Quit the loop: %s\n", modbus_strerror(errno));

__error:
    modbus_mapping_free(mb_mapping);
    if(socket > 0)
        modbus_close(tcp_ctx);
    modbus_free(tcp_ctx);
    if(nRet > 0)
        modbus_close(rtu_ctx);
    modbus_free(rtu_ctx);

    return 0;
}

你可能感兴趣的:(libmodbus学习(三)之Modbus协议转换)