交叉编译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
编译完成后,在install生成三个目录: include, lib, share,里面是我们需要用到的库文件与头文件。
lib文件夹下的libmodbus.so.5.1.0即为可以被调用的库文件,在实际编程的时候需要包含头文件:#include"modbus.h"
本章节是笔者在进行libmodbus开发时,根据项目需求对源码进行修改以获得新的功能特性。由于libmodbus已经对串口进行了较为完善的封装,我们无法利用这个库发送自定义格式的一些数据(某些设备或者从机在某些情况下并不是采用标准的modbus协议,需要我们自己自定义每一帧的数据)。因此,本章节的修改主要目的在于开放串口发送任意数据的API,已增加库的扩展性与通用性。同时,笔者需要在轮询modbus接收数据的时候不造成阻塞,开放了结构体 modbus_t的内部,以获得串口文件句柄,然后借助select机制将读取串口数据变为不阻塞的。
进入到./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-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: "<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()<
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);
}