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]结束。

我的代码例子中代码很多不完善,而且没有对收到的数据进行解析,是否需要解析,根据个人需求。

头文件:

#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
View Code

源文件:

#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;
}
View Code

例子说明,我用到Peugeot.exe测试,这个软件的tcp模式也是串口帧传输的(有点反人类)。图中第6步注意服务器ip。设置好后,两个对话框都要点击 “确定”

 个人程序页面:

 

posted @ 2024-01-15 18:59  阳光下的小土豆  阅读(196)  评论(0编辑  收藏  举报