libmodbus 源码分析: 1. ?uint8_t 的头文件 ?#include "stdint.h"? 2. ? 两个最核心的结构体
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;
struct _modbus { ? ? /* Slave address */ ? ? int slave; ? ? /* Socket or file descriptor */ ? ? int s; ? ? int debug; ? ? int error_recovery; ? ? struct timeval response_timeout; ? ? struct timeval byte_timeout; ? ? struct timeval indication_timeout; ? ? const modbus_backend_t *backend; ? //把各种操作封装在一个结构体中,也可以不封装。 ? ? void *backend_data; };
typedef struct _modbus modbus_t;
这两个结构体定义在 modbus-private.h ?从中可以借鉴到一点 在取名时不对外的结构体或函数命名可以在前面加 "_"
3. ?应用: ----------------------------random-test-client.c-------------------------- 过程 modbus_t ctx = modbus_new_tcp("127.0.0.1", 1502); ? // ctx表示容器 ?在这里既可以表示cli 也可以表示ser modbus_connect(ctx); modbus_read_registers() ?位于modbus.c协议核心层 ? ->read_registers ? ? ? 位于modbus.c协议核心层 ? ? ?-> ? ?req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req); ?//构建requeset基础信息
? ? ? ? ? ?rc = send_msg(ctx, req, req_length); ? ? ? ? ? ?if (rc > 0) {
? ? ? ? ? ?rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); ? ? ? ? ? ?rc = check_confirmation(ctx, req, rsp, rc); ? ? ? ? ? ?offset = ctx->backend->header_length; ? ? ? ? ? ? for (i = 0; i < rc; i++) { ? ? ? ? ? ? /* shift reg hi_byte to temp OR with lo_byte */ ? ? ? ? ? ? dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | ? ? ? ? ? ? ? ? rsp[offset + 3 + (i << 1)]; ? ? ? ? ? ?} ?? ??? ? ? return dest;
?? ??? ? ?? 4. modbus_new_tcp 函数过程: ?? modbus_new_tcp-》 ? ? modbus_t *ctx; ? ? modbus_tcp_t *ctx_tcp; ? ? ? ctx = (modbus_t *)malloc(sizeof(modbus_t));} ? ? ? _modbus_init_common(ctx); ?//给ctx 赋初始值 ?不管是modbus_tcp 还是modbus_rtu都是调用这个函数。 ?? ? ? ? ? ctx->slave = MODBUS_TCP_SLAVE; ? ? ? //tcp?
? ? ctx->backend = &_modbus_tcp_backend; //tcp ?_modbus_tcp_backend 这个是一个已经定义并初始化了的结构体。 位于modbus-tcp.c
? ? ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t)); ?//tcp ?分配内存 ?? ? ?? ?ctx_tcp = (modbus_tcp_t *)ctx->backend_data; ?? ?->根据modbus_new_tcp 传入的参数填充ctx_tcp 也就是ctx->backend_data ?? ?>end
?? ?? 5. ?? ?? ?? ?? // 具体的modbus_backend_t?? ?? ?? ?modbus-tcp.c ?_modbus_tcp_backend结构体变量。 全局 const modbus_backend_t _modbus_tcp_backend = { ? ? _MODBUS_BACKEND_TYPE_TCP, ? ? _MODBUS_TCP_HEADER_LENGTH, ? ? _MODBUS_TCP_CHECKSUM_LENGTH, ? ? MODBUS_TCP_MAX_ADU_LENGTH, ? ? _modbus_set_slave, ? ? _modbus_tcp_build_request_basis, ? ? _modbus_tcp_build_response_basis, ? ? _modbus_tcp_prepare_response_tid, ? ? _modbus_tcp_send_msg_pre, ? ? _modbus_tcp_send, ? ? _modbus_tcp_receive, ? ? _modbus_tcp_recv, ? ? _modbus_tcp_check_integrity, ? ? _modbus_tcp_pre_check_confirmation, ? ? _modbus_tcp_connect, ? ? _modbus_tcp_close, ? ? _modbus_tcp_flush, ? ? _modbus_tcp_select, ? ? _modbus_tcp_free };?? ?? ?? ?? 6.?? ?? ?? ?? //初始化modbus的公共部分 void _modbus_init_common(modbus_t *ctx) { ? ? /* Slave and socket are initialized to -1 */ ? ? ctx->slave = -1; ? ? ctx->s = -1;
? ? ctx->debug = FALSE; ? ? ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;
? ? ctx->response_timeout.tv_sec = 0; ? ? ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;
? ? ctx->byte_timeout.tv_sec = 0; ? ? ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;
? ? ctx->indication_timeout.tv_sec = 0; ? ? ctx->indication_timeout.tv_usec = 0; }
7. ----------------------------random-test-server.c-------------------------- modbus_mapping_t定义了modbus的四种寄存器,并进行了内存数据映射,以方便快速访问和读取寄存器的值。
其他跟client差不多,多了一个modbus_mapping_new
modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int nb_registers, int nb_input_registers) { ? ? return modbus_mapping_new_start_address( ? ? ? ? 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); }
modbus_mapping_new_start_address :映射内存,实际是分配了4个内存。
实现如下: ? ? modbus_mapping_t *mb_mapping;
? ? mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t)); //申请内存 ? ? /* 0X */ ? ? mb_mapping->nb_bits = nb_bits; ? nb:numbers 表示个数。 ? ? mb_mapping->start_bits = start_bits; ? ? mb_mapping->tab_bits =(uint8_t *) malloc(nb_bits * sizeof(uint8_t)); 注意这里的长度 ? ? memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t));
main函数实现: ? ? modbus_t *ctx; ? ? modbus_mapping_t *mb_mapping; ? ? ctx = modbus_new_tcp("127.0.0.1", 1502); ? ? mb_mapping = modbus_mapping_new(500, 500, 500, 500); ? ? s = modbus_tcp_listen(ctx, 1); ? ? modbus_tcp_accept(ctx, &s); ? ? rc = modbus_receive(ctx, query); ?? ?//free malloc ? ? modbus_mapping_free(mb_mapping); ? ? modbus_close(ctx); ? ? modbus_free(ctx);
8. 超时处理 如果需要精确到ms us可以 也可以参考这里面的。
比如里面用到延时的例子: static void _sleep_response_timeout(modbus_t *ctx) { ? ? /* Response timeout is always positive */ #ifdef _WIN32 ? ? /* usleep doesn't exist on Windows */ ? ? Sleep((ctx->response_timeout.tv_sec * 1000) + ? ? ? ? ? (ctx->response_timeout.tv_usec / 1000)); #else ? ? /* usleep source code */ ? ? struct timespec request, remaining; ? ? request.tv_sec = ctx->response_timeout.tv_sec; ? ? request.tv_nsec = ((long int)ctx->response_timeout.tv_usec) * 1000; ? ? while (nanosleep(&request, &remaining) == -1 && errno == EINTR) { ? ? ? ? request = remaining; ? ? } #endif } ?? ? ?? ? 9.困惑已久的问题:使用enum 或#define ?场景: 如果所有的整型值是连续的建议使用enum,如果中间可能会有非连续的建议使用#define? 例如 libmodubs里面: /* Protocol exceptions */ enum { ? ? MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, ? ? MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, ? ? MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, ? ? MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, ? ? MODBUS_EXCEPTION_ACKNOWLEDGE, ? ? MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, ? ? MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, ? ? MODBUS_EXCEPTION_MEMORY_PARITY, ? ? MODBUS_EXCEPTION_NOT_DEFINED, ? ? MODBUS_EXCEPTION_GATEWAY_PATH, ? ? MODBUS_EXCEPTION_GATEWAY_TARGET, ? ? MODBUS_EXCEPTION_MAX };
/* Modbus function codes */ #define MODBUS_FC_READ_COILS ? ? ? ? ? ? ? ?0x01 #define MODBUS_FC_READ_DISCRETE_INPUTS ? ? ?0x02 #define MODBUS_FC_READ_HOLDING_REGISTERS ? ?0x03 #define MODBUS_FC_READ_INPUT_REGISTERS ? ? ?0x04 #define MODBUS_FC_WRITE_SINGLE_COIL ? ? ? ? 0x05 #define MODBUS_FC_WRITE_SINGLE_REGISTER ? ? 0x06 #define MODBUS_FC_READ_EXCEPTION_STATUS ? ? 0x07 #define MODBUS_FC_WRITE_MULTIPLE_COILS ? ? ?0x0F #define MODBUS_FC_WRITE_MULTIPLE_REGISTERS ?0x10 #define MODBUS_FC_REPORT_SLAVE_ID ? ? ? ? ? 0x11 #define MODBUS_FC_MASK_WRITE_REGISTER ? ? ? 0x16 #define MODBUS_FC_WRITE_AND_READ_REGISTERS ?0x17
#define MODBUS_BROADCAST_ADDRESS ? ?0
10 #define 宏定义 #define a b ,后接两个参数,表示用a代替b。 例如 :#define PI 3.14 ? ? ? ?#define uint8_t ?unsigned char?
#define 后只有一个参数 定义宏替换为空字符串,可以理解为后一个参数为空字符串
11.?宏函数与函数: 一般来说,应该用宏去替换小的、可重复的代码段,这样可以使程序运行速度更快;当任务比较复杂,需要多行代码才能实现时,或者要求程序越小越好时,就应该使用函数。
11.总结:libmodubs 构建了两个结构体 modbus_t 和 modbus_backend_t ,其中modbus_t用来表示modbus client或者server 通称为ctx(容器),modbus_backend_t 封装了各种modbus操作,作为指针放置在modbus_t 里面。
modbus_backend_t * 指向不同的具体类型设备 tcp/rtu。重点在于理解面向对象的设计方法
|