libmodbus tcp/ip client

1.modbus概况

搜索有大把的概括,感兴趣就搜,我就精简的写。

  • 是一种通讯协议,分客户端/主机(client/master)和服务端/从机(server/slave)。
  • 客户端/主机 向 服务器/从机 发送报文读写数据。
  • 服务器/从机 只能在客户端/主机查询时返回数据,不能主动向客户端/主机发送数据(当然可以改协议)。

2.各语言的推荐 Modbus 库

3. 我对modbus程序的理解

废话不多说,直接看程序。

server设备一直维护四个table:

  • Coils: 保存二进制数据,可读可写
  • Discrete inputs: 保存二进制数据,只可读
  • input registers: 保存16进制数据,只可读
  • holding registers: 保存16进制数据,可读可写

client设备发送报文读/写server数据,报文中需要包含的内容:

  • server地址(因为一个client可以连接多个server,需要指定哪个server)
  • function功能码(对哪个寄存器表,进行读或写)
  • 寄存器具体地址
  • 和function功能码对应的具体数据

4.tcp/ip 报文解析

其实如果不打算做处理报文的程序,单纯只维护寄存器表时,这部分不用看,因为各个开源程序已经封装好处理报文的程序,调用函数就行。

4.1 功能码(libmodbus 程序中的定义)
  • 0x01 :MODBUS_FC_READ_COILS ,读cols
  • 0x02 :MODBUS_FC_READ_DISCRETE_INPUTS,读discrete inputs
  • 0x03 :MODBUS_FC_READ_HOLDING_REGISTERS,读holding register
  • 0x04 :MODBUS_FC_READ_INPUT_REGISTERS, 读input register
  • 0x05 :MODBUS_FC_WRITE_SINGLE_COIL, 写一个 coil
  • 0x06 :MODBUS_FC_WRITE_SINGLE_REGISTER, 写一个register
  • 0x07 :MODBUS_FC_READ_EXCEPTION_STATUS, 读exception status
  • 0x0F :MODBUS_FC_WRITE_MULTIPLE_COILS ,写多个coils
  • 0x10 :MODBUS_FC_WRITE_MULTIPLE_REGISTERS ,写多个register
  • 0x11 :MODBUS_FC_REPORT_SLAVE_ID, 上报地址
  • 0x16 :MODBUS_FC_MASK_WRITE_REGISTER ,对一个数据进行处理
  • 0x17 :MODBUS_FC_WRITE_AND_READ_REGISTERS ,读写registers
4.2 client向server发送 的request报文

报文构成:

帧头tcp/ip协议表示该字节以后的长度设备地址功能码数据
2字节0x00 002字节1字节1字根据功能码确定
4.2.1 client发送报文,各个功能码对应的数据部分
  • 读操作: 0x01, 0x02, 0x03, 0x04
2字节2字节
寄存器起始地址读寄存器的数目
  • 0x05: 写一个coil
2字节2字节
寄存器起始地址数据(0x0000 ,代表0; 0xff00,代表1)
  • 0x06,写一个register
2字节2字节
寄存器起始地址数据
  • 0x0f,写多个coils
2字节2字节一个字节写数据个数/8 个字节
寄存器起始地址写数据个数写数据个数/8具体算法看代码即可

例如写9个数据,第三部分数值为0x02

  • 0x10 :写多个register
2字节2字节一个字节2*n 个字节
寄存器起始地址写数据个数后面的字节数目写每个数据两个字节
  • 0x11 : 上报地址
2字节
寄存器起始地址
  • 0x07, 读exception status
2字节
寄存器起始地址
  • 0x16 : 处理数据
2字节2个字节2个字节
寄存器起始地址andor

最终数据为

data = (data & and) | (or & (~and));
  • 0x17 : 读写
2字节2个字节2个字节2字节1字节2*(写寄存器数目n) 字节
读寄存器起始地址读寄存器数目写寄存器起始地址写寄存器数目之后的字节数写寄存器数值
4.2.2 报文举例

举例:读holding register

03 6D00 0000 06010300 0000 0A
主机发送的检验信息(client发送的request的次序)表示tcp/ip的协议是modbus协议表示该字节以后的长度设备地址功能码寄存器起始地址读寄存器数目
4.3 server的response 报文

报文构成

帧头tcp/ip协议表示长度设备地址功能码数据长度数据
2字节0x00 002字节1字节1字节1字节由具体功能决定

举例:返回holding register数据

03 6D00 0000 1701031400 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
检验信息tcp/ip为modbus协议表示该字节后的长度设备地址功能码该字节后的字节数

具体返回的数据部分看代码,懒得写了

5.写个代码(libmodbus) tcp/ip client

了解完就开干。 github搞到源代码,其中还有test例程可以参考。

#include "modbus/modbus.h"
class myclass:{
public:
    myclass();
	virtual ~myclass();
	void listenDevice();
private:
    std::string ip_;
	int port_;
	int server_address_;
	modbus_t* ctx_;
	modbus_mapping_t* mb_mapping_;
	std::shared_ptr<std::thread> revThread_;
	bool timer_run_;
}
myclass::myclass()
{
	ip_ = "127.0.0.1"; //ip
	port_ = 502;  //端口号
	server_address_ = 1;  //地址
	timer_run_ = true;
	ctx_ = modbus_new_tcp(ip_.c_str(),port_);  //初始化
	mb_mapping_ = NULL;   //数据字典
	if(ctx_ == NULL){
		printf("Unable to allocate libmodbus context :%s",modbus_strerror(errno));
	}
	printf("finished_init");
	revThread_ =  std::make_shared<std::thread>(std::bind(&myclass::listenDevice,this));
}

myclass::~myclass() {
	timer_run_ = false;
	if(ctx_ != NULL){
		modbus_free(ctx_);
	}
	if(mb_mapping_ !=NULL){
		modbus_mapping_free(mb_mapping_);
	}
}

void myclass::listenDevice(){
	while(1){
		printf("begin listen device");
		if(!timer_run_){
			return;
		}
		if(ctx_ == NULL){
			printf("Unable to allocate libmodbus context");
			return;
		}
		modbus_set_debug(ctx_,true);
		
		//server需要listen
		int server_socket = modbus_tcp_listen(ctx_,10);
		if(server_socket == -1){
			printf("Unable to listen TCP: %s",modbus_strerror(errno));
			continue;
		}

		modbus_tcp_accept(ctx_, &server_socket);
		//初始数据字典
		mb_mapping_ = modbus_mapping_new(MODBUS_MAX_READ_BITS,
										MODBUS_MAX_WRITE_BITS,
										MODBUS_MAX_READ_REGISTERS,
										MODBUS_MAX_WRITE_REGISTERS);
		if (mb_mapping_ == NULL) {
			printf("Failed to allocate the mapping: %s",modbus_strerror(errno));
			continue;
		}
		//设置字典的数据
	    for (int i=0;i<20;i++)
	    {
	    	mb_mapping_->tab_input_registers[i] = i;
	    	mb_mapping_->tab_registers[i] = i;
	    }

	    while(1){
	    	uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
	    	//接受client报文
	    	int rc = modbus_receive(ctx_, query);
	    	if (rc > 0) {
	    	    //处理client报文
	    		modbus_reply(ctx_, query, rc, mb_mapping_);
	    	} else{
	    		printf("error %s\n", modbus_strerror(errno));
	    		break;
	    	}
			if(!timer_run_){
				return;
			}
	    }
		close(server_socket);
	}
}

来自client的报文解析在modbus_reply中。源代码看一下就好,结构很清晰,想处理报文就改这部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值