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_t modbus_tcp_t的区别和联系:
modbus_t 定义了modbus rtu/tcp/asccii 的公共属性和接口。
modbus_tcp_t : 定义tcp 独有属性。
modbus_new_tcp 函数过程:
函数功能:给 modbus_t 变量分配内存并初始化
特别的,这里会用到一个 modbus_tcp_t 的结构体来初始化其私有数据变量ctx->backend_data
1. 分配内存
ctx = (modbus_t *)malloc(sizeof(modbus_t));
ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t)); //私有数据
2. 函数初始化
ctx->backend = &_modbus_tcp_backend; init函数指针
3. 私有数据初始化
ctx_tcp = (modbus_tcp_t *)ctx->backend_data; // modbus_tcp_t ctx_tcp;
填充数据
ctx_rtu->confirmation_to_ignore = FALSE;
ctx_rtu->baud = baud;
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);
7.2 read函数API 调用过程
typedef struct _modbus modbus_t;
rc = modbus_read_bits(ctx, UT_BITS_ADDRESS, UT_BITS_NB, tab_rp_bits);
-> rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest);
-> rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
-> rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read); 最终调用到这里, 由上文 已经初始化好的结构体modbus_backend_t 函数指针指向的函数。
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 里面。
12 总结:
modbus_backend_t * 指向不同的具体类型设备 tcp/rtu。重点在于理解面向对象的设计方法