modbus开源库 libmodbus
近期用到了modbus,搜到了libmodbus开源库,记录一下。网上关于如何编译的文章很多,不再赘述。
借鉴:https://blog.csdn.net/qq_38158479/article/details/120928043
说明:本文代码改造为适于windows的qt。
为了多线程业务处理,类继承QObject,这里作为服务器(子站)。代码和例子位于文末。例子和测试软件下载
说明一下个人对代码中寄存器首地址和数量的理解。
在类cmanagemodbus(tcp服务器)中,以线圈举例:_modbus_mapping_t中,初始化线圈元素个数(nb_bits)是60000,如果首地址(start_bits)是1024,占用元素数量是100个,则线圈数组的数据应该从tab_bits[1024]开始写入,到tab_bits[1123]结束。
我的代码例子中代码很多不完善,而且没有对收到的数据进行解析,是否需要解析,根据个人需求。
头文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#ifndef CMANAGEMODBUS_H #define CMANAGEMODBUS_H #include <iostream> #include <thread> #include <stdlib.h> #include <iostream> #include <mutex> #include <string> #include <QObject> #include <QThread> /*如果是windows平台则要加载相应的静态库和头文件*/ #ifdef _WIN32 #define _WINSOCK_DEPRECATED_NO_WARNINGS #include <winsock2.h> #include <windows.h> #include "libmodbus/modbus.h" #pragma comment(lib, "Ws2_32.lib") //#pragma comment/*(lib, "modbus.lib")*/ /*linux平台*/ #else #include <modbus/modbus.h> #include <unistd.h> #include <error.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #endif using namespace std; //#define MAX_POINT 50000 //coil status 线圈寄存器 使用功能码:01H(读);05H(写单个位);0FH(写多个位) //input status 离散输入寄存器 :02H //holding register 保持寄存器 :03H(读);06H(写单个字节);0FH(写多个字节) //input register 输入寄存器:04H enum DataType { TypeYxData = 0, TypeYcData, TypeYtData, TypeYkData, TypeYmData, TypeUnknown }; #pragma pack(1) //配置信息 struct S_ModbusConfigInfo { int slaveId;//子站地址 int yxFuncCode;//遥信功能码 规定为02 int ycFuncCode;//遥测功能码 规定为03 int yxStartAddr;//遥信首地址 int ycStartAddr;//遥测首地址 int ymStartAddr;//遥脉首地址 int yxNum;//遥信数量 int ycNum;//遥测数量 int ymNum;//遥脉数量 S_ModbusConfigInfo() { yxFuncCode = 0x02; ycFuncCode = 0x03; } }; #pragma pack() Q_DECLARE_METATYPE(S_ModbusConfigInfo) //注册结构体 class CManageModbus : public QObject { Q_OBJECT public: explicit CManageModbus(string host="0.0.0.0", uint16_t port=502, bool bServer = true, bool bRemoveHead = true, QObject* parent = nullptr);//bServer 启动的是否是服务器 ~CManageModbus(); public: void recieveMessages();//收取信息 bool modbus_set_slave_id(int id);//设置设备识别码id bool initModbus(std::string Host_Ip, int port, bool debugging);//初始化modbus uint8_t getTab_Input_Bits(int NumBit); bool setTab_Input_Bits(int NumBit, uint8_t Value);//向首地址写入 寄存器写入 uint16_t getHoldingRegisterValue(int registerNumber); float getHoldingRegisterFloatValue(int registerStartaddress); bool setHoldingRegisterValue(int registerNumber, uint16_t Value); bool setHoldingRegisterValue(int registerNumber, float Value); bool setInputRegisterValue(int registerNumber, uint16_t Value); bool setInputRegisterValue(int registerNumber, float Value); void closeModbus();//关闭modbus void stopThread();//退出线程 void getConfigInfo(S_ModbusConfigInfo &configInfo);//设置界面配置信息,服务器开始时,收到信号更新配置信息 void getTableData(int nDataType, uint16_t *pData, int nDataLen); void getTableData(int nDataType, uint8_t *pData, int nDataLen);//表格数据写入到mapping signals: void sigAddLog(bool bServer, bool bRecv, QString msg); public slots: void doWork();//线程函数 private: std::mutex slavemutex; int m_errCount{ 0 }; int m_modbusSocket{ -1 }; bool m_initialized{ false }; modbus_t* ctx{ nullptr };//modbus 实例 modbus_mapping_t* mapping{ nullptr }; /*Mapping*/ int m_numBits{ 60000 };//线圈, (数组元素个数) int m_numInputBits{ 60000 };//离散输入, (数组元素个数) int m_numRegisters{ 60000 };//输入寄存器(数组元素个数) int m_numInputRegisters{ 60000 };// 保持寄存器个数(数组元素个数) bool m_bStopThread;//控制线程退出 bool m_bServer;//是否服务器 bool m_bRemoveHead;//tcp模式去掉协议头(只保留装置地址,和peugeot.exe保持一致) S_ModbusConfigInfo m_configInfo; private: void loadFromConfigFile(); void run(); QString unchar2QString(unsigned char* pIn, int nLen);//unsingned char 转16进制字符串 int QStringHex2Char(const QString& strIn, uint8_t *out, int lenSpace);//16禁止字符串转unsigned char,QString中必须是类似"16 05 78","160578",5必须补为05 void addMBAPhead(uint8_t *query, int& rc);//接收到无报文头的,加上 void removeMBAPhead(uint8_t *query, int& rc);//发送时,再去掉报文头 void buildSendMapping(const uint8_t *req, int req_len); void buildYcMapping(uint16_t *pData, int nDataLen);//遥测对应 0x03 void buildYxMapping(uint8_t *pData, int nDataLen);//目前遥信对应 0x02 void buildYkMapping(uint8_t *pData, int nDataLen);//向map void buildYtMapping(uint8_t *pData, int nDataLen); void buildYmMapping(uint8_t *pData, int nDataLen); unsigned int ModBusCRC16(unsigned char *data, unsigned int len); }; /*annotation: (1)https://www.jianshu.com/p/0ed380fa39eb (2)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;*/ #endif // CMANAGEMODBUS_H
源文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include "cmanagemodbus.h" #include <string.h> #include <QDebug> #ifdef WIN32 typedef int socklen_t; #endif /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief modbus initialization * @param IP/PORT/debugflag * @version v1 * @return null * @date 2021/10/6 **************************************************************/ bool CManageModbus::initModbus(std::string Host_Ip = "127.0.0.1", int port = 502, bool debugging = true) { ctx = modbus_new_tcp(Host_Ip.c_str(), port); //modbus_set_debug(ctx, debugging); if (ctx == NULL) { fprintf(stderr, "There was an error allocating the modbus\n"); throw - 1; } m_modbusSocket = modbus_tcp_listen(ctx, 1); /*设置线圈, 离散输入, 输入寄存器, 保持寄存器个数(数组元素个数))*/ mapping = modbus_mapping_new(m_numBits, m_numInputBits, m_numInputRegisters, m_numRegisters); if (mapping == NULL) { fprintf(stderr, "Unable to assign mapping:%s\n", modbus_strerror(errno)); modbus_free(ctx); m_initialized = false; return false; } m_initialized = true; return true; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief Constructor * @version v1 * @return null * @date 2021/10/6 **************************************************************/ CManageModbus::CManageModbus(string host, uint16_t port, bool bServer, bool bRemoveHead, QObject* parent) :QObject(parent) { m_bServer = bServer; m_bStopThread = false; m_bRemoveHead = bRemoveHead; initModbus(host, port, false); //TODO: } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief Destructor * @version v1 * @return null * @date 2021/10/6 **************************************************************/ CManageModbus::~CManageModbus() { if(!ctx) { modbus_mapping_free(mapping); modbus_close(ctx); modbus_free(ctx); } } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief loadFromConfigFile * @version v1 * @return null * @date 2021/10/18 **************************************************************/ void CManageModbus::loadFromConfigFile() { return; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief run * @version v1 * @return null * @date 2021/10/18 **************************************************************/ void CManageModbus::run() { std::thread loop([this]() { while (true) { if (m_initialized) { recieveMessages(); } else { m_initialized = true; } } }); loop.detach(); return; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief modbus_set_slave_id * @param id * @version v1 * @return null * @date 2021/10/19 **************************************************************/ bool CManageModbus::modbus_set_slave_id(int id) { int rc = modbus_set_slave(ctx, id); if (rc == -1) { fprintf(stderr, "Invalid slave id\n"); modbus_free(ctx); return false; } return true; } bool CManageModbus::setInputRegisterValue(int registerStartaddress, uint16_t Value) { if (registerStartaddress > (m_numRegisters - 1)) { return false; } slavemutex.lock(); mapping->tab_input_registers[registerStartaddress] = Value; slavemutex.unlock(); return true; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief setRegisterValue(设置保存寄存器的值,类型为uint16_t) * @param registerStartaddress(保存寄存器的起始地址) * @param Value(写入到保存寄存器里的值) * @version v1 * @return null * @date 2021/10/6 **************************************************************/ bool CManageModbus::setHoldingRegisterValue(int registerStartaddress, uint16_t Value) { if (registerStartaddress > (m_numRegisters - 1)) { return false; } slavemutex.lock(); mapping->tab_registers[registerStartaddress] = Value; slavemutex.unlock(); return true; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief getRegisterValue(获取保存寄存器的值) * @param registerStartaddress(保存寄存器的起始地址) * @version v1 * @return null * @date 2021/10/6 **************************************************************/ uint16_t CManageModbus::getHoldingRegisterValue(int registerStartaddress) { if (!m_initialized) { return -1; } return mapping->tab_registers[registerStartaddress]; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief setTab_Input_Bits(设置输入寄存器某一位的值) * @param NumBit(输入寄存器的起始地址) * @param Value(输入寄存器的值) * @version v1 * @return null * @date 2021/10/8 **************************************************************/ bool CManageModbus::setTab_Input_Bits(int NumBit, uint8_t Value) { if (NumBit > (m_numInputBits - 1)) { return false; } slavemutex.lock(); mapping->tab_input_bits[NumBit] = Value; slavemutex.unlock(); return true; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief getTab_Input_Bits(获取输入寄存器某一位的值) * @param NumBit(输入寄存器相应的bit位) * @version v1 * @return null * @date 2021/10/8 **************************************************************/ uint8_t CManageModbus::getTab_Input_Bits(int NumBit) { if (!m_initialized) { return -1; } return mapping->tab_input_bits[NumBit]; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief setRegisterFloatValue(设置浮点值) * @param (Value:浮点值,registerStartaddress寄存器起始地址) * @version v1 * @return null * @date 2021/10/8 **************************************************************/ bool CManageModbus::setHoldingRegisterValue(int registerStartaddress, float Value) { if (registerStartaddress > (m_numRegisters - 2)) { return false; } /*小端模式*/ slavemutex.lock(); modbus_set_float(Value, &mapping->tab_registers[registerStartaddress]); slavemutex.unlock(); return true; } bool CManageModbus::setInputRegisterValue(int registerStartaddress, float Value) { if (registerStartaddress > (m_numRegisters - 2)) { return false; } /*小端模式*/ slavemutex.lock(); modbus_set_float(Value, &mapping->tab_input_registers[registerStartaddress]); slavemutex.unlock(); return true; } void CManageModbus::closeModbus() { closesocket(m_modbusSocket); modbus_mapping_free(mapping); modbus_close(ctx); modbus_free(ctx); ctx = nullptr; } void CManageModbus::stopThread() { m_bStopThread = true; } void CManageModbus::doWork() { while (!m_bStopThread) { if (m_initialized) { recieveMessages(); } else { m_initialized = true; } } int i = 1; } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief 获取寄存器里的浮点数 * @param registerStartaddress寄存器起始地址 * @version v1 * @return 两个uint16_t拼接而成的浮点值 * @date 2021/10/6 **************************************************************/ float CManageModbus::getHoldingRegisterFloatValue(int registerStartaddress) { if (!m_initialized) { return -1.0f; } return modbus_get_float_badc(&mapping->tab_registers[registerStartaddress]); } /*************************************************************** * @file CManageModbus.cpp * @author seer-txj * @brief 支持多个master同时连接 * @version v1 * @return null * @date 2021/10/6 **************************************************************/ void CManageModbus::recieveMessages() { uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; int master_socket; int rc; fd_set refset; fd_set rdset; /* Maximum file descriptor number */ int fdmax; /* Clear the reference set of socket */ FD_ZERO(&refset); /* Add the server socket */ FD_SET(m_modbusSocket, &refset); /* Keep track of the max file descriptor */ fdmax = m_modbusSocket; int nCount = 0; QString msg; while( true ) { nCount++; rdset = refset; if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1) { perror("Server select() failure."); break; } /* Run through the existing connections looking for data to be * read */ for (master_socket = 0; master_socket <= fdmax; master_socket++) { if (!FD_ISSET(master_socket, &rdset)) { continue; } //qDebug() << "fdmax = " << fdmax << "master_socket = " << master_socket; if (master_socket == m_modbusSocket) { /* A client is asking a new connection */ socklen_t addrlen; struct sockaddr_in clientaddr; int newfd; /* Handle new connections */ addrlen = sizeof(clientaddr); memset(&clientaddr, 0, sizeof(clientaddr)); newfd = accept(m_modbusSocket, (struct sockaddr *)&clientaddr, &addrlen); if (newfd == -1) { perror("Server accept() error"); } else { FD_SET(newfd, &refset); if (newfd > fdmax) { /* Keep track of the maximum */ fdmax = newfd; } printf("New connection from %s:%d on socket %d\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd); } msg = QString("New connection from %1:%2 on socket %3").arg(inet_ntoa(clientaddr.sin_addr)).arg(clientaddr.sin_port).arg(newfd); qDebug() << msg; emit sigAddLog(m_bServer, true, msg); } else { modbus_set_socket(ctx, master_socket); rc = modbus_receive(ctx, query); if (rc > 0) { msg = unchar2QString(query, rc); emit sigAddLog(m_bServer, true, msg);//接收到的数据显示到列表 if(m_bRemoveHead) { addMBAPhead(query, rc);//添加头 } // QString str = "01 02 05 00 00 00 00 00 A2 52"; // uint8_t* send = new uint8_t[str.size()]; // int len = QStringHex2Char(str, send, 1); int nSendLen = 0; uint8_t *pSend = new uint8_t[260]; memset(pSend, 0, 260); int errcode = modbus_reply(ctx, query, rc, mapping, pSend, &nSendLen, m_bRemoveHead?1:0); if(errcode == -1) { msg = "发送失败"; } else { msg = unchar2QString(pSend, nSendLen); } delete[] pSend; pSend = nullptr; emit sigAddLog(m_bServer, false, msg); } else if (rc == -1) { /* This example server in ended on connection closing or * any errors. */ /* 20240111屏蔽 msg = QString("Connection closed on socket %1").arg(master_socket); emit sigAddLog(m_bServer, false, msg); #ifdef _WIN32 closesocket(master_socket); #else close(master_socket); #endif //Remove from reference set FD_CLR(master_socket, &refset); if (master_socket == fdmax) { fdmax--; } */ } } } } qDebug() << "out recieve"; m_initialized = false; } QString CManageModbus::unchar2QString(unsigned char *pIn, int nLen) { QString temp; QString msg; int j = 0; while(j < nLen) { temp = QString("%1 ").arg((int)pIn[j], 2, 16, QLatin1Char('0')); msg.append(temp); j++; } return msg; } int CManageModbus::QStringHex2Char(const QString &strIn, uint8_t *out, int lenSpace) { uint8_t* p = nullptr; if (strIn.length() < 2) { return 0; } int offset = lenSpace + 2;//每次偏移2+间隔字符长度 int j = 0; for (int i = 0; i < strIn.length(); i += offset) { QString num = strIn.mid(i, 2); bool ok = false; out[j] = num.toUInt(&ok, 16); j++; if (!ok) { return 0; } } return j; } void CManageModbus::addMBAPhead(uint8_t *query, int& rc) { memcpy(query + 6, query, rc); memset(query, 0, 6); rc += 6; } void CManageModbus::removeMBAPhead(uint8_t *query, int &rc) { memcpy(query, query + 6, rc - 6); rc -= 6; } void CManageModbus::buildSendMapping(const uint8_t *req, int req_len) { } void CManageModbus::getTableData(int nDataType, uint8_t *pData, int nDataLen) { switch (nDataType) { case TypeYxData: buildYxMapping(pData, nDataLen); break; case TypeYcData: //buildYcMapping(pData, nDataLen); break; case TypeYtData: buildYtMapping(pData, nDataLen); break; case TypeYkData: buildYkMapping(pData, nDataLen); break; case TypeYmData: buildYxMapping(pData, nDataLen); break; default: break; } } void CManageModbus::getConfigInfo(S_ModbusConfigInfo &configInfo) { modbus_set_slave_id(configInfo.slaveId); m_configInfo = configInfo; } void CManageModbus::getTableData(int nDataType, uint16_t *pData, int nDataLen) { switch (nDataType) { case TypeYxData: //buildYxMapping(pData, nDataLen); break; case TypeYcData: buildYcMapping(pData, nDataLen); break; case TypeYtData: //buildYtMapping(pData, nDataLen); break; case TypeYkData: //buildYkMapping(pData, nDataLen); break; case TypeYmData: //buildYxMapping(pData, nDataLen); break; default: break; } } void CManageModbus::buildYcMapping(uint16_t *pData, int nDataLen) { if(mapping != nullptr) { memcpy(mapping->tab_registers + m_configInfo.ycStartAddr, pData, nDataLen * 2);//由于是uint16,所以得*2 } int i = 1; } void CManageModbus::buildYxMapping(uint8_t *pData, int nDataLen) { if(mapping != nullptr) { memcpy(mapping->tab_input_bits + m_configInfo.yxStartAddr, pData, nDataLen); } } void CManageModbus::buildYkMapping(uint8_t *pData, int nDataLen) { if(mapping != nullptr) { memcpy(mapping->tab_registers, pData, nDataLen); } } void CManageModbus::buildYtMapping(uint8_t *pData, int nDataLen) { if(mapping != nullptr) { memcpy(mapping->tab_registers, pData, nDataLen); } } void CManageModbus::buildYmMapping(uint8_t *pData, int nDataLen) { if(mapping != nullptr) { memcpy(mapping->tab_registers, pData, nDataLen); } } unsigned int CManageModbus::ModBusCRC16(unsigned char *data, unsigned int len) { unsigned int i, j, tmp, CRC16; CRC16 = 0xFFFF; //CRC寄存器初始值 for (i = 0; i < len; i++) { CRC16 ^= data[i]; for (j = 0; j < 8; j++) { tmp = (unsigned int)(CRC16 & 0x0001); CRC16 >>= 1; if (tmp == 1) { CRC16 ^= 0xA001; //异或多项式 } } } /*根据需要对结果进行处理*/ // data[i++] = (unsigned char) (CRC16 & 0x00FF); // data[i++] = (unsigned char) ((CRC16 & 0xFF00)>>8); return CRC16; }
例子说明,我用到Peugeot.exe测试,这个软件的tcp模式也是串口帧传输的(有点反人类)。图中第6步注意服务器ip。设置好后,两个对话框都要点击 “确定”
个人程序页面: