版本和開發環境說明
- libmodbus版本爲 3.1.6
- 交叉編譯host爲 Ubuntu16.04
- 交叉編譯target爲 arm A7內核的imx6ull
- 交叉編譯器爲 arm-linux-gnueabihf-gcc
操作步驟
交叉編譯libmodbus的流程相對簡單:
#use alias setcc to config cross compile environment
setcc
cd ./libmodbus-3.1.6
./configure --host=arm-linux-gnueabihf --prefix=$(pwd)/install
make & make install
- 如果需要靜態庫,則./configure加上選項:
–enable-static - 如果需要動態庫,則./configure加上選項:
–enable-shared
編譯完成後,在install生成三個目錄: include, lib, share,裏面是我們需要用到的庫文件與頭文件。
lib文件夾下的libmodbus.so.5.1.0即爲可以被調用的庫文件,在實際編程的時候需要包含頭文件:#include"modbus.h"
源碼的修改
本章節是筆者在進行libmodbus開發時,根據項目需求對源碼進行修改以獲得新的功能特性。由於libmodbus已經對串口進行了較爲完善的封裝,我們無法利用這個庫發送自定義格式的一些數據(某些設備或者從機在某些情況下並不是採用標準的modbus協議,需要我們自己自定義每一幀的數據)。因此,本章節的修改主要目的在於開放串口發送任意數據的API,已增加庫的擴展性與通用性。同時,筆者需要在輪詢modbus接收數據的時候不造成阻塞,開放了結構體 modbus_t的內部,以獲得串口文件句柄,然後藉助select機制將讀取串口數據變爲不阻塞的。
開放串口發送任意數據的API
進入到./libmodbus-3.1.6/src/中,修改modbus.c與modbus.h文件:
在modbus.c中定義函數:
int modbus_send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
{
return ctx->backend->send(ctx, msg, msg_length);
}
在modbus.h中添加聲明:
MODBUS_API int modbus_send_msg(modbus_t *ctx, uint8_t *msg, int msg_length);
即可在項目代碼中調用modbus_send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)函數發送任意數據。
輪詢modbus接收數據時不阻塞
在modbus-rtu.c中,函數:
static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
{
#if defined(_WIN32)
return win32_ser_read(&((modbus_rtu_t *)ctx->backend_data)->w_ser, rsp, rsp_length);
#else
return read(ctx->s, rsp, rsp_length);
#endif
}
由於採用了read機制,會使得程序在輪詢接收數據的時候(在modbus.c中),造成阻塞。
int modbus_receive(modbus_t *ctx, uint8_t *req)
{
if (ctx == NULL)
{
errno = EINVAL;
return -1;
}
return ctx->backend->receive(ctx, req);
}
修改代碼,開放***typedef struct _modbus modbus_t***的內部,以獲得串口文件句柄,然後藉助select機制將讀取串口數據變爲不阻塞的。具體操作如下:
將modbus-private.h中關於_modbus結構體的定義、_modbus_backend結構體、_sft結構體的定義直接複製到已經編譯好的庫“./install/include/modbus/modbus.h”中。不需要修改源碼,亦不影響編譯過程。
/* This structure reduces the number of params in functions and so
* optimizes the speed of execution (~ 37%). */
typedef struct _sft {
int slave;
int function;
int t_id;
} sft_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;
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;
};
modbus_t的成員s即串口文件句柄(Socket or file descriptor)
項目代碼示例
在實際項目中(以C++/Qt4.8爲例)可以通過以下方式輪詢modbus,將接收數據變爲不阻塞:
modbus_mapping_t *mb_mapping;
modbus_t *ctx;
void ModbusThread::run()
{
int rc;
fd_set set;
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 1000;
// int use_backend;
//初始化modbus rtu
ctx = modbus_new_rtu(SLAVE_PORT, 9600, 'N', 8, 1);
//設定從設備地址
modbus_set_slave(ctx, SLAVE_ADDR);
//modbus連接
modbus_connect(ctx);
//qDebug()<<"serial port fd is: "<<ctx->s;
//寄存器map初始化
mb_mapping = modbus_mapping_new(MODBUS_MAX_WRITE_BITS, MODBUS_MAX_READ_BITS , MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_READ_REGISTERS);
if (mb_mapping == NULL)
{
fprintf(stderr, "Failed to allocate the mapping: %s\n",modbus_strerror(errno));
modbus_free(ctx);
return;
}
if (isRemoteMonitoringUsed_dialog == true)
{
qDebug()<<"isRemoteMonitoringUsed_dialog:"<<isRemoteMonitoringUsed_dialog;
mainWidget->timer_uploadDataToCloud.start(10000);
qDebug()<<"Remote Monitoring is used.";
}
else {
qDebug()<<"isRemoteMonitoringUsed_dialog:"<<isRemoteMonitoringUsed_dialog;
qDebug()<<"Remote Monitoring is not used.";
}
while(true)
{
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
SyncModbusRegisters();
FD_ZERO(&set);
FD_SET(ctx->s, &set); //此處將讀取的串口fd加入隊列
select(ctx->s+1,&set,NULL,NULL,&timeout); //此次判斷讀取的隊列
if(!FD_ISSET(ctx->s, &set))
{
continue;
}
//輪詢接收數據,並做相應處理
QMutexLocker locker1(&modbusPortLock);
rc = modbus_receive(ctx, query);
locker1.unlock();
if (rc > 0)
{
//QString receivedData = "";
//for(int i=0; i< rc ; i++)
//{
// receivedData.append(QString::number(query[i],16)).append(" ");
//}
//qDebug()<<receivedData;
modbus_reply(ctx, query, rc, mb_mapping);
ModbusSyncAction();
}
else if (rc == -1)
{
//qDebug()<<"Modbus connection closed by the client or error.";
//break;
}
else
{
}
QMutexLocker locker(&m_lock);
if(!isThreadLoop)
{
qDebug()<<"isThreadLoop:"<<isThreadLoop;
break;
}
}
printf("Modbus thread is terminated: %s\n", modbus_strerror(errno));
modbus_mapping_free(mb_mapping);
/* For RTU, skipped by TCP (no TCP connect) */
modbus_close(ctx);
modbus_free(ctx);
}