LibModbus基本信息
GitHub: https://github.com/stephane/libmodbus.git
Branch: Master
Commit:docs: fix simple typo, reponse -> response
Version: 3.1.6
數(shù)據(jù)結(jié)構(gòu)
消息類型
/*
* ---------- 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代表主設(shè)備發(fā)送的輪詢指令
- MSG_CONFIRMATION代表從設(shè)備回復(fù)的響應(yīng)指令
協(xié)議解析狀態(tài)
/* 3 steps are used to parse the query */
typedef enum {
_STEP_FUNCTION,
_STEP_META,
_STEP_DATA
} _step_t;
libmodbus根據(jù)狀態(tài)機(jī)模式讀取協(xié)議垦巴,三種狀態(tài)定義如下:
- _STEP_FUNCTION 讀取操作碼
- _STEP_META 讀取固定字段(寄存器等)
- _STEP_DATA 讀取后續(xù)數(shù)據(jù)
modbus_t 結(jié)構(gòu)體成員大多數(shù)在_modbus_init_common
函數(shù)中初始化
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 從機(jī)地址
- s Socket文件描述符
- debug 是否調(diào)試模式浊闪,默認(rèn)false
- error_recovery 是否錯(cuò)誤恢復(fù)匣缘,默認(rèn)不恢復(fù)
MODBUS_ERROR_RECOVERY_NONE
- response_timeout master發(fā)送消息等待響應(yīng)的時(shí)長(zhǎng)近哟,在
_modbus_init_common
函數(shù)中被初始化為ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;//(500000us == 500ms)
- byte_timeout 協(xié)議接收過(guò)程中下一個(gè)字節(jié)的等待時(shí)長(zhǎng)岩瘦,默認(rèn)為
_BYTE_TIMEOUT(500000us == 500ms)
- indication_timeout 從設(shè)備接收MSG_INDICATION消息的等待時(shí)長(zhǎng)尝苇,默認(rèn)為0荣瑟,
_modbus_receive_msg
函數(shù)中有if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) { p_tv = NULL;...}
征炼,select系統(tǒng)調(diào)用的timtout為NULL則阻塞,所以默認(rèn)從設(shè)備接收 MSG_INDICATION會(huì)進(jìn)入阻塞模式 - backend modbus的核心操作函數(shù)集合
- backend_data rtu/tcp/tcp_pi端口相關(guān)數(shù)據(jù)
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
函數(shù)中初始化了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
};
數(shù)據(jù)接收
Modbus Slaver接收函數(shù)
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最終調(diào)用了_modbus_receive_msg
函數(shù)吼鱼,該函數(shù)有個(gè)msg_type_t
類型參數(shù)蓬豁,代表當(dāng)前讀取的消息是MSG_INDICATION
or MSG_CONFIRMATION
。
confirmation_to_ignore在初始化時(shí)默認(rèn)為false菇肃,因此地粪,_modbus_rtu_receive
函數(shù)大多數(shù)時(shí)候是在執(zhí)行_modbus_receive_msg(ctx, req, MSG_INDICATION);
指令,即接收MSG_INDICATION消息琐谤,接收成功蟆技,下一步將ctx_rtu->confirmation_to_ignore = TRUE
,開始執(zhí)行_modbus_receive_msg(ctx, req, MSG_CONFIRMATION)
,接收MSG_CONFIRMATION消息,不管是否成功都重新將ctx_rtu->confirmation_to_ignore = FALSE
,又開始接收MSG_INDICATION消息斗忌。
Modbus Master讀取寄存器函數(shù)
/* 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
發(fā)送寄存器讀取指令rc = send_msg(ctx, req, req_length);
质礼,然后通過(guò)rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
讀取從機(jī)的響應(yīng),函數(shù)直接調(diào)用了_modbus_receive_msg()
飞蹂,并且以MSG_CONFIRMATION
作為消息類型几苍,代表讀取一條Modbus響應(yīng)協(xié)議.
_modbus_receive_msg接收函數(shù)
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);
}
本函數(shù)根據(jù)msg_type_t msg_type
參數(shù)及modbus_t *ctx
上下文嘗試讀取并解析一條modbus協(xié)議,基本流程如下:
- 讀取過(guò)程Linux主要采用
select系統(tǒng)調(diào)用
陈哑,win封裝了win32_ser_select
函數(shù) - 解析過(guò)程采用狀態(tài)機(jī)模型進(jìn)行解析妻坝,參考文章開頭數(shù)據(jù)結(jié)構(gòu)“協(xié)議解析狀態(tài)”
這里拿幾個(gè)MSG_INDICATION
協(xié)議舉例,MSG_CONFIRMATION
消息同理:
- [01 03 00 14 00 02 84 0F] 讀保持寄存器惊窖,從0014開始刽宪,讀取0002個(gè)寄存器
- [01 06 00 14 01 99 08 34] 寫單個(gè)保持寄存器,地址為0014界酒,寫入數(shù)據(jù)為:01 99
- [01 10 00 14 00 02 04 AA BB CC DD 37 F4] 寫多個(gè)保持寄存器圣拄,從0014開始,寫入0002個(gè)寄存器毁欣,共04字節(jié)庇谆,寫入數(shù)據(jù)為:AA BB CC DD
解析過(guò)程中,根據(jù)階段計(jì)算應(yīng)該讀取的長(zhǎng)度
- _STEP_FUNCTION階段: length_to_read固定為2個(gè)字節(jié)
03協(xié)議:length_to_read = 2
06協(xié)議:length_to_read = 2
10協(xié)議:length_to_read = 2 - _STEP_META階段: 具體參考
compute_meta_length_after_function
函數(shù)
03協(xié)議:length_to_read = 4
06協(xié)議:length_to_read = 4
10協(xié)議:length_to_read = 5 - _STEP_DATA階段:具體參考
compute_data_length_after_meta
函數(shù)
03協(xié)議:length_to_read = 0
06協(xié)議:length_to_read = 0
10協(xié)議:length_to_read = 5
寄存器
數(shù)據(jù)結(jié)構(gòu)
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 線圈 每一個(gè)bit對(duì)應(yīng)一個(gè)信號(hào)的開關(guān)狀態(tài)岳掐,單位是
bit
- Discrete Inputs 離散量輸入 相當(dāng)于線圈寄存器的只讀模式,單位是
bit
- Input Registers 輸入寄存器 和下面的保持寄存器類似饭耳,單位是
U16
- Holding Registers 保持寄存器 單位不再是bit而是兩個(gè)byte串述,單位是
U16
結(jié)構(gòu)體成員 - nb_bits/start/bits/tab_bits 對(duì)應(yīng) 線圈的數(shù)量/起始地址/內(nèi)存空間
- nb_input_bits/start_input_bits/tab_input_bits 對(duì)應(yīng) 離散輸入的數(shù)量/起始地址/內(nèi)存空間
- nb_input_registers/start_input_registers/tab_input_registers 對(duì)應(yīng)輸入寄存器的數(shù)量/起始地址/內(nèi)存空間
- nb_registers/start_registers/tab_registers 對(duì)應(yīng)保持寄存器的數(shù)量/起始地址/內(nèi)存空間
分配空間
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
調(diào)用了modbus_mapping_new_start_address
函數(shù),將四種寄存器的起始地址都設(shè)定為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
函數(shù)按照給定的起始地址和數(shù)量分配寄存器空間寞肖。
讀寫寄存器
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
函數(shù)根據(jù)收到的消息纲酗,對(duì)寄存器進(jìn)行操作,并向master回復(fù)消息.