libmodbus协议栈2—— Linux下 modbus RTU master 开发案例

   使用libmodbus库进行应用程序master(主机)开发,基本顺序如下:

1. 初始化 modbus 指针
2. 设置从站ID
3. 建立连接
4. 读取保持寄存器/输入寄存器/离散输入/线圈输入
5. 写单个寄存器/多个寄存器/多位数据
6. 关闭连接

     libmodbus为了支持跨平台,采用了名为后端(backends)来进行通信,这个其实可以理解为函数指针,针对不同的通信协议(modbus RTU、modbus TCP),对应不同的函数。也就是API函数名字是固定的,但是这个函数名背后指向的 函数却不同。

    理解libmodbus函数,个人觉得最重要的数据结构就是 _modbus 结构体,这个结构体内容如下:

struct _modbus {
    /* Slave address */
    int slave;                          //设备地址,主、从
    /* Socket or file descriptor */
    int s;                              //设备文件描述符:串口设备、socket
    int debug;                          // debug 标志,置位,则会输出各种过程调试信息
    int error_recovery;                 // 故障恢复
    struct timeval response_timeout;    // 数据响应时间 结构体
    struct timeval byte_timeout;        // 字节超时 时间结构体
    const modbus_backend_t *backend;    // 后端函数指针,所有的函数表头
    void *backend_data;                 // 后端数据
};

   进一步,我们再看下 modbus_backend_t 结构体,如下:

typedef struct _modbus_backend {
    unsigned int backend_type;
    unsigned int header_length;
    unsigned int checksum_length;
    unsigned int max_adu_length;
    int (*set_slave) (modbus_t *ctx, int slave);
    int (*build_request_basis) (modbus_t *ctx, int function, int addr,
                                int nb, uint8_t *req);
    int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
    int (*prepare_response_tid) (const uint8_t *req, int *req_length);
    int (*send_msg_pre) (uint8_t *req, int req_length);
    ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
    int (*receive) (modbus_t *ctx, uint8_t *req);
    ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
    int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
                            const int msg_length);
    int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
                                   const uint8_t *rsp, int rsp_length);
    int (*connect) (modbus_t *ctx);
    void (*close) (modbus_t *ctx);
    int (*flush) (modbus_t *ctx);
    int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
    void (*free) (modbus_t *ctx);
} modbus_backend_t;

    不出所料,这个结构体包含了各种函数指针,所以前面的modbus_backend_t *backend就是这些所有 函数表的表头。我们在进行modbus结构体初始化的时候,libmodbus会自动的赋值 对应的  处理函数。

    libmodbus提供的API函数,可以参考官方提供的手册,接下来我们举个例子,下面的例子实现设备通信协议为 modbus rtu,设备是 master 主机,程序如下:

/*
*---------------------------------------
*
*        modbus-rtu-master.c
*
*---------------------------------------
*/

#include 
#include 
#include 
#include 
#include 
#include  //这个是必须的,相当于引用了 libmodbus库的头文件
 

 
int main(int argc, char *argv[])
{
    uint16_t  tab_reg[64] = {0};         //定义存放数据的数组
    modbus_t *ctx = NULL;                // modbus_t 指针定义
 
    int rc;
	int i;
	//以串口的方式创建libmobus实例,并设置参数
    //使用UART1,对应的设备描述符为ttySP0			
	ctx = modbus_new_rtu("/dev/ttySP0", 9600, 'N', 8, 1);  //相当于初始化 modbus_t
	if (ctx == NULL)               
	{
    	fprintf(stderr, "Unable to allocate libmodbus contex\n");
    	return -1;
	}
	
	modbus_set_debug(ctx, 1);      //设置1可看到调试信息
	modbus_set_slave(ctx, 1);      //设置slave ID
	
	if (modbus_connect(ctx) == -1) //等待连接设备
	{
    	fprintf(stderr, "Connection failed:%s\n", modbus_strerror(errno));
    	return -1;
	}
	
	while (1)
	{
    	printf("\n----------------\n");
    	rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    	if (rc == -1)                      //读取保持寄存器的值,可读取多个连续输入保持寄存器
    	{
			fprintf(stderr,"%s\n", modbus_strerror(errno));
			return -1;
    	}
    	for (i=0; i<10; i++)
    	{
			printf("reg[%d] = %d(0x%x)\n", i, tab_reg[i], tab_reg[i]);
    	}
		
    	sleep(1);
	}
    modbus_close(ctx);  //关闭modbus连接
	modbus_free(ctx);   //释放modbus资源,使用完libmodbus需要释放掉
 
	return 0;
}

  上面的程序包含了各种错误处理,我们简单总结梳理下:

1. 引用 modbus.h ,必须的。

2. 使用modbus_new_rtu 函数实现 modbus rtu的初始化,初始化的返回 一个modbus_t 指针, 这个指针包含了所有modbus功能,所以这个指针的返回,是通过malloc分配了一段内存空间,这个内存空间是独一的,其实也必须这么做,因为这个结构体中不光有各种函数指针,还有各种数据,所以必须要单独分配一段内存空间。而且如果不这么做的话,势必会容易被别的函数误操作,所以必须要申请一段独立内存。modbus的所有操作,都是基于该指针,相当于文件描述符。

3. 设置调试模式modbus_set_debug,非必须。

4. 设置设备地址 modbus_set_slave,这个设备地址其实是两说了,既可以是要读的从设备地址,又可以是自
   身地址,具体地址如何,完全取决于后面的应用程序。

5. 建立连接modbus_connect(ctx)。

6. 进入应用逻辑,比如主循环或单次通信等。

7. modbus_read_registers(ctx, 0, 10, tab_reg),没错,区别当前设备是主机还是从机的区别就在这里,我们调用该函数,就是告诉libmodbus要作为主设备对从设备进行读操作,起始地址为0,总共读10个保持寄存器,当然读输入寄存器、输入线圈等还有其对应的读函数。需要说明的是,这个函数背后有很多的操作:

7.1 打包读命令。

7.2 发送读命令。

7.3 使用select机制,接收串口状态。

7.5 接收数据后,对数据进行筛选处理,将筛选后的数据,也就是读到的寄存器内容存到 tab_reg中。

8. 应用程序停止modbus通信时,先关闭modbus,比如关闭串口,或关闭 先释放socket,然后释放步骤1中那个modbus内存区域。

 

你可能感兴趣的:(Linux)