LibModbus基本信息
GitHub: https://github.com/stephane/libmodbus.git
Branch: Master
Commit:docs: fix simple typo, reponse -> response
Version: 3.1.6
数据结构
消息类型
/*
* ---------- Request Indication ----------
* | Client | ---------------------->| Server |
* ---------- Confirmation Response ----------
*/
typedef enum {
/* Request message on the server side */
MSG_INDICATION,
/* Request message on the client side */
MSG_CONFIRMATION
} msg_type_t;
- MSG_INDICATION代表主设备发送的轮询指令
- MSG_CONFIRMATION代表从设备回复的响应指令
协议解析状态
/* 3 steps are used to parse the query */
typedef enum {
_STEP_FUNCTION,
_STEP_META,
_STEP_DATA
} _step_t;
libmodbus根据状态机模式读取协议,三种状态定义如下:
- _STEP_FUNCTION 读取操作码
- _STEP_META 读取固定字段(寄存器等)
- _STEP_DATA 读取后续数据
modbus_t 结构体成员大多数在_modbus_init_common函数中初始化
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;
};
- slaver 从机地址
- s Socket文件描述符
- debug 是否调试模式,默认false
- error_recovery 是否错误恢复,默认不恢复
MODBUS_ERROR_RECOVERY_NONE - response_timeout master发送消息等待响应的时长,在
_modbus_init_common函数中被初始化为ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;//(500000us == 500ms) - byte_timeout 协议接收过程中下一个字节的等待时长,默认为
_BYTE_TIMEOUT(500000us == 500ms) - indication_timeout 从设备接收MSG_INDICATION消息的等待时长,默认为0,
_modbus_receive_msg函数中有if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) { p_tv = NULL;...},select系统调用的timtout为NULL则阻塞,所以默认从设备接收 MSG_INDICATION会进入阻塞模式 - backend modbus的核心操作函数集合
- backend_data rtu/tcp/tcp_pi端口相关数据
ctx->backend_data = (modbus_rtu_t *)malloc(sizeof(modbus_rtu_t));
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_new_rtu函数中初始化了backend成员ctx->backend = &_modbus_rtu_backend;
const modbus_backend_t _modbus_rtu_backend = {
_MODBUS_BACKEND_TYPE_RTU,
_MODBUS_RTU_HEADER_LENGTH,
_MODBUS_RTU_CHECKSUM_LENGTH,
MODBUS_RTU_MAX_ADU_LENGTH,
_modbus_set_slave,
_modbus_rtu_build_request_basis,
_modbus_rtu_build_response_basis,
_modbus_rtu_prepare_response_tid,
_modbus_rtu_send_msg_pre,
_modbus_rtu_send,
_modbus_rtu_receive,
_modbus_rtu_recv,
_modbus_rtu_check_integrity,
_modbus_rtu_pre_check_confirmation,
_modbus_rtu_connect,
_modbus_rtu_close,
_modbus_rtu_flush,
_modbus_rtu_select,
_modbus_rtu_free
};
数据接收
Modbus Slaver接收函数
static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
{
int rc;
modbus_rtu_t *ctx_rtu = ctx->backend_data;
if (ctx_rtu->confirmation_to_ignore) {
_modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
/* Ignore errors and reset the flag */
ctx_rtu->confirmation_to_ignore = FALSE;
rc = 0;
if (ctx->debug) {
printf("Confirmation to ignore\n");
}
} else {
rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
if (rc == 0) {
/* The next expected message is a confirmation to ignore */
ctx_rtu->confirmation_to_ignore = TRUE;
}
}
return rc;
}
_modbus_rtu_receive最终调用了_modbus_receive_msg函数,该函数有个msg_type_t类型参数,代表当前读取的消息是MSG_INDICATION or MSG_CONFIRMATION。
confirmation_to_ignore在初始化时默认为false,因此,_modbus_rtu_receive函数大多数时候是在执行_modbus_receive_msg(ctx, req, MSG_INDICATION);指令,即接收MSG_INDICATION消息,接收成功,下一步将ctx_rtu->confirmation_to_ignore = TRUE,开始执行_modbus_receive_msg(ctx, req, MSG_CONFIRMATION),接收MSG_CONFIRMATION消息,不管是否成功都重新将ctx_rtu->confirmation_to_ignore = FALSE,又开始接收MSG_INDICATION消息。
Modbus Master读取寄存器函数
/* Reads the data from a remove device and put that data into an array */
static int read_registers(modbus_t *ctx, int function, int addr, int nb,
uint16_t *dest)
{
int rc;
int req_length;
uint8_t req[_MIN_REQ_LENGTH];
uint8_t rsp[MAX_MESSAGE_LENGTH];
if (nb > MODBUS_MAX_READ_REGISTERS) {
if (ctx->debug) {
fprintf(stderr,
"ERROR Too many registers requested (%d > %d)\n",
nb, MODBUS_MAX_READ_REGISTERS);
}
errno = EMBMDATA;
return -1;
}
req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);
rc = send_msg(ctx, req, req_length);
if (rc > 0) {
int offset;
int i;
rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
if (rc == -1)
return -1;
rc = check_confirmation(ctx, req, rsp, rc);
if (rc == -1)
return -1;
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 rc;
}
read_registers 发送寄存器读取指令rc = send_msg(ctx, req, req_length);,然后通过rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);读取从机的响应,函数直接调用了_modbus_receive_msg(),并且以MSG_CONFIRMATION作为消息类型,代表读取一条Modbus响应协议.
_modbus_receive_msg接收函数
int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
int rc;
fd_set rset;
struct timeval tv;
struct timeval *p_tv;
int length_to_read;
int msg_length = 0;
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
printf("Waiting for an indication...\n");
} else {
printf("Waiting for a confirmation...\n");
}
}
/* Add a file descriptor to the set */
FD_ZERO(&rset);
FD_SET(ctx->s, &rset);
/* We need to analyse the message step by step. At the first step, we want
* to reach the function code because all packets contain this
* information. */
step = _STEP_FUNCTION;
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION) {
/* Wait for a message, we don't know when the message will be
* received */
if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) {
/* By default, the indication timeout isn't set */
p_tv = NULL;
} else {
/* Wait for an indication (name of a received request by a server, see schema) */
tv.tv_sec = ctx->indication_timeout.tv_sec;
tv.tv_usec = ctx->indication_timeout.tv_usec;
p_tv = &tv;
}
} else {
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
while (length_to_read != 0) {
rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
if (rc == -1) {
_error_print(ctx, "select");
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
int saved_errno = errno;
if (errno == ETIMEDOUT) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
} else if (errno == EBADF) {
modbus_close(ctx);
modbus_connect(ctx);
}
errno = saved_errno;
}
return -1;
}
rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
if (rc == 0) {
errno = ECONNRESET;
rc = -1;
}
if (rc == -1) {
_error_print(ctx, "read");
if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
(errno == ECONNRESET || errno == ECONNREFUSED ||
errno == EBADF)) {
int saved_errno = errno;
modbus_close(ctx);
modbus_connect(ctx);
/* Could be removed by previous calls */
errno = saved_errno;
}
return -1;
}
/* Display the hex code of each character received */
if (ctx->debug) {
int i;
for (i=0; i < rc; i++)
printf("<%.2X>", msg[msg_length + i]);
}
/* Sums bytes received */
msg_length += rc;
/* Computes remaining bytes */
length_to_read -= rc;
if (length_to_read == 0) {
switch (step) {
case _STEP_FUNCTION:
/* Function code position */
length_to_read = compute_meta_length_after_function(
msg[ctx->backend->header_length],
msg_type);
if (length_to_read != 0) {
step = _STEP_META;
break;
} /* else switches straight to the next step */
case _STEP_META:
length_to_read = compute_data_length_after_meta(
ctx, msg, msg_type);
if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) {
errno = EMBBADDATA;
_error_print(ctx, "too many data");
return -1;
}
step = _STEP_DATA;
break;
default:
break;
}
}
if (length_to_read > 0 &&
(ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
/* If there is no character in the buffer, the allowed timeout
interval between two consecutive bytes is defined by
byte_timeout */
tv.tv_sec = ctx->byte_timeout.tv_sec;
tv.tv_usec = ctx->byte_timeout.tv_usec;
p_tv = &tv;
}
/* else timeout isn't set again, the full response must be read before
expiration of response timeout (for CONFIRMATION only) */
}
if (ctx->debug)
printf("\n");
return ctx->backend->check_integrity(ctx, msg, msg_length);
}
本函数根据msg_type_t msg_type参数及modbus_t *ctx上下文尝试读取并解析一条modbus协议,基本流程如下:

- 读取过程Linux主要采用
select系统调用,win封装了win32_ser_select函数 - 解析过程采用状态机模型进行解析,参考文章开头数据结构“协议解析状态”
这里拿几个MSG_INDICATION协议举例,MSG_CONFIRMATION消息同理:
- [01 03 00 14 00 02 84 0F] 读保持寄存器,从0014开始,读取0002个寄存器
- [01 06 00 14 01 99 08 34] 写单个保持寄存器,地址为0014,写入数据为:01 99
- [01 10 00 14 00 02 04 AA BB CC DD 37 F4] 写多个保持寄存器,从0014开始,写入0002个寄存器,共04字节,写入数据为:AA BB CC DD
解析过程中,根据阶段计算应该读取的长度
- _STEP_FUNCTION阶段: length_to_read固定为2个字节
03协议:length_to_read = 2
06协议:length_to_read = 2
10协议:length_to_read = 2 - _STEP_META阶段: 具体参考
compute_meta_length_after_function函数
03协议:length_to_read = 4
06协议:length_to_read = 4
10协议:length_to_read = 5 - _STEP_DATA阶段:具体参考
compute_data_length_after_meta函数
03协议:length_to_read = 0
06协议:length_to_read = 0
10协议:length_to_read = 5
寄存器
数据结构
typedef struct _modbus_mapping_t {
int nb_bits;
int start_bits;
int nb_input_bits;
int start_input_bits;
int nb_input_registers;
int start_input_registers;
int nb_registers;
int start_registers;
uint8_t *tab_bits;
uint8_t *tab_input_bits;
uint16_t *tab_input_registers;
uint16_t *tab_registers;
} modbus_mapping_t;
modbus共有四种寄存器
- COILS 线圈 每一个bit对应一个信号的开关状态,单位是
bit - Discrete Inputs 离散量输入 相当于线圈寄存器的只读模式,单位是
bit - Input Registers 输入寄存器 和下面的保持寄存器类似,单位是
U16 - Holding Registers 保持寄存器 单位不再是bit而是两个byte,单位是
U16
结构体成员 - nb_bits/start/bits/tab_bits 对应 线圈的数量/起始地址/内存空间
- nb_input_bits/start_input_bits/tab_input_bits 对应 离散输入的数量/起始地址/内存空间
- nb_input_registers/start_input_registers/tab_input_registers 对应输入寄存器的数量/起始地址/内存空间
- nb_registers/start_registers/tab_registers 对应保持寄存器的数量/起始地址/内存空间
分配空间
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调用了modbus_mapping_new_start_address函数,将四种寄存器的起始地址都设定为0.
/* Allocates 4 arrays to store bits, input bits, registers and inputs
registers. The pointers are stored in modbus_mapping structure.
The modbus_mapping_new_start_address() function shall return the new allocated
structure if successful. Otherwise it shall return NULL and set errno to
ENOMEM. */
modbus_mapping_t* modbus_mapping_new_start_address(
unsigned int start_bits, unsigned int nb_bits,
unsigned int start_input_bits, unsigned int nb_input_bits,
unsigned int start_registers, unsigned int nb_registers,
unsigned int start_input_registers, unsigned int nb_input_registers)
{
modbus_mapping_t *mb_mapping;
mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t));
if (mb_mapping == NULL) {
return NULL;
}
/* 0X */
mb_mapping->nb_bits = nb_bits;
mb_mapping->start_bits = start_bits;
if (nb_bits == 0) {
mb_mapping->tab_bits = NULL;
} else {
/* Negative number raises a POSIX error */
mb_mapping->tab_bits =
(uint8_t *) malloc(nb_bits * sizeof(uint8_t));
if (mb_mapping->tab_bits == NULL) {
free(mb_mapping);
return NULL;
}
memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t));
}
/* 1X */
mb_mapping->nb_input_bits = nb_input_bits;
mb_mapping->start_input_bits = start_input_bits;
if (nb_input_bits == 0) {
mb_mapping->tab_input_bits = NULL;
} else {
mb_mapping->tab_input_bits =
(uint8_t *) malloc(nb_input_bits * sizeof(uint8_t));
if (mb_mapping->tab_input_bits == NULL) {
free(mb_mapping->tab_bits);
free(mb_mapping);
return NULL;
}
memset(mb_mapping->tab_input_bits, 0, nb_input_bits * sizeof(uint8_t));
}
/* 4X */
mb_mapping->nb_registers = nb_registers;
mb_mapping->start_registers = start_registers;
if (nb_registers == 0) {
mb_mapping->tab_registers = NULL;
} else {
mb_mapping->tab_registers =
(uint16_t *) malloc(nb_registers * sizeof(uint16_t));
if (mb_mapping->tab_registers == NULL) {
free(mb_mapping->tab_input_bits);
free(mb_mapping->tab_bits);
free(mb_mapping);
return NULL;
}
memset(mb_mapping->tab_registers, 0, nb_registers * sizeof(uint16_t));
}
/* 3X */
mb_mapping->nb_input_registers = nb_input_registers;
mb_mapping->start_input_registers = start_input_registers;
if (nb_input_registers == 0) {
mb_mapping->tab_input_registers = NULL;
} else {
mb_mapping->tab_input_registers =
(uint16_t *) malloc(nb_input_registers * sizeof(uint16_t));
if (mb_mapping->tab_input_registers == NULL) {
free(mb_mapping->tab_registers);
free(mb_mapping->tab_input_bits);
free(mb_mapping->tab_bits);
free(mb_mapping);
return NULL;
}
memset(mb_mapping->tab_input_registers, 0,
nb_input_registers * sizeof(uint16_t));
}
return mb_mapping;
}
modbus_mapping_new_start_address函数按照给定的起始地址和数量分配寄存器空间。
读写寄存器
int modbus_reply(modbus_t *ctx, const uint8_t *req,
int req_length, modbus_mapping_t *mb_mapping)
{
//...省略
/* Data are flushed on illegal number of values errors. */
switch (function) {
case MODBUS_FC_READ_COILS:
case MODBUS_FC_READ_DISCRETE_INPUTS: {
//...省略
}
break;
case MODBUS_FC_READ_HOLDING_REGISTERS:
case MODBUS_FC_READ_INPUT_REGISTERS: {
//...省略
}
break;
case MODBUS_FC_WRITE_SINGLE_COIL: {
//...省略
}
break;
case MODBUS_FC_WRITE_SINGLE_REGISTER: {
int mapping_address = address - mb_mapping->start_registers;
if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_register\n",
address);
} else {
int data = (req[offset + 3] << 8) + req[offset + 4];
mb_mapping->tab_registers[mapping_address] = data;
memcpy(rsp, req, req_length);
rsp_length = req_length;
}
}
break;
case MODBUS_FC_WRITE_MULTIPLE_COILS: {
//...省略
}
break;
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
int nb = (req[offset + 3] << 8) + req[offset + 4];
int nb_bytes = req[offset + 5];
int mapping_address = address - mb_mapping->start_registers;
if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal number of values %d in write_registers (max %d)\n",
nb, MODBUS_MAX_WRITE_REGISTERS);
} else if (mapping_address < 0 ||
(mapping_address + nb) > mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_registers\n",
mapping_address < 0 ? address : address + nb);
} else {
int i, j;
for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) {
/* 6 and 7 = first value */
mb_mapping->tab_registers[i] =
(req[offset + j] << 8) + req[offset + j + 1];
}
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
/* 4 to copy the address (2) and the no. of registers */
memcpy(rsp + rsp_length, req + rsp_length, 4);
rsp_length += 4;
}
}
break;
case MODBUS_FC_REPORT_SLAVE_ID: {
//...省略
}
break;
case MODBUS_FC_READ_EXCEPTION_STATUS:
//...省略
break;
case MODBUS_FC_MASK_WRITE_REGISTER: {
int mapping_address = address - mb_mapping->start_registers;
if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_register\n",
address);
} else {
uint16_t data = mb_mapping->tab_registers[mapping_address];
uint16_t and = (req[offset + 3] << 8) + req[offset + 4];
uint16_t or = (req[offset + 5] << 8) + req[offset + 6];
data = (data & and) | (or & (~and));
mb_mapping->tab_registers[mapping_address] = data;
memcpy(rsp, req, req_length);
rsp_length = req_length;
}
}
break;
case MODBUS_FC_WRITE_AND_READ_REGISTERS: {
//...省略
}
break;
default:
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_FUNCTION, rsp, TRUE,
"Unknown Modbus function code: 0x%0X\n", function);
break;
}
/* Suppress any responses when the request was a broadcast */
return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length);
}
modbus_reply函数根据收到的消息,对寄存器进行操作,并向master回复消息.