MODBUS规约的秘密之五-----如何用C++编写MODBUS规约
根据之前的几篇MODBUS规约介绍,我们发现MODBUS规约是比较简单的一个规约,
如上图,我们可以看到MODBUS_RTU和MODBUS_TCP之间有共同的部分,因此可以考虑用C++中的继承方式。将上图中的红框部分用一个基类来实现,串口方式和网络方式分别继承自此基类。这样就可以同时实现即支持MODBUS_RTU又支持MODBUS_TCP,相当于将MODBUS分成了应用层和链路层。使用共同的应用层代码维护也比较方便。
基类可以这样写:
class CModbusS
{
public:
CModbusS();
protected:
BOOL IsTerminated();
private:
BOOL m_bIsTerminated;
CSync sync_Tag;
bool ProcessSOE();
bool ProcessYX();
CSync m_Sync_Event; // 事件队列用互斥
void AddToEventQueue(TYxValue &signalValue,TSignalAddress &SignalAddr);
// 读取事件,一次一条
BOOL ReadEventQuent(TYxValue &signalValue,TSignalAddress &SignalAddr);
list<TYXData> m_lSOE; // 遥信事件队列
list<TYXData>::iterator m_itrEventPos;
BOOL EventQueueEmpty();
void ActEvent(); // 确认已经发送的事件
public:
//CModbusS();
//~CModbusS();
// 前两个参数,报文和报文实际长度,offset实际报文开始
// 报文主要用于显示,偏移用于实际解析报文,对于TCP,偏移为7个字节,对于串口,偏移为1个字节,实际都从处理功能码开始
int AnalyseDiagram(LPBYTE szDiag,int len,int offset=7);
virtual int WriteTele(LPBYTE szDiag, WORD length)=0;
void SetStopTag(BOOL bFlag);
virtual void MainControlS();
void InitSignalParam();
virtual void * LinkLayerMain()=0;
WORD CreateExceptionDiag(LPBYTE szDiag,BYTE btExCode);
};
这个类中主要实现就是分析报文函数AnalyseDiagram,处理都是从功能码开始的,之所以传入全部报文,还是为了打印显示报文用。处理报文不处理报文头和校验码,只处理串口模式和网络模式中的共同部分。
报文报文中需要调用的处理遥信等函数放到了私有函数中。
MainControlS为应用层主处理函数,LinkLayerMain为链路层处理函数,定义为纯虚函数,需要继承实现。
发送报文函数WriteTele也定义为纯虚函数,需要继承实现,基类只负责将需要发送的应用层报文传入,由继承类来增加头尾并进行具体发送。
class CMODBUSSCOM: public CModbusS{
int WriteTele(LPBYTE szDiag, WORD length);
void * LinkLayerMain();
CSerial m_Comm;
WORD suMod_buildCRC16(
unsigned char *puchMsg, /* message to calculate CRC upon */
unsigned short usDataLen /* quantity of bytes in message */
);
};
以上为MODBUS_RTU的继承实现,主要实现发送函数WriteTele和链路层函数。suMod_buildCRC16这个为计算校验码函数,网上和文档中都有很多具体实现。
class CMODBUSSTCP : public CModbusS{
public:
CMODBUSSTCP();
CConnection *m_conn;
string m_strCurIP; // 当前连接件的IP
private:
CNetworking m_myNet;
void StopListen();
bool StartListen();
void CheckConn();
BYTE m_btSendBuf[SEND_BUF_LEN]; // 发送缓冲
int WriteTele(LPBYTE szDiag, WORD length);
void * LinkLayerMain();
};
以上为MODBUS_TCP的继承类定义。同样只需要实现发送函数和链路层函数。由于这里展示是从规约,因此有启动监听和停止监听函数。主规约是类似的,用TCP的连接函数即可。
if(g_ModbussParam.bMODBUSTCP)
g_ModbussParam.pModbus = new CMODBUSSTCP();
else
g_ModbussParam.pModbus = new CMODBUSSCOM();
在main函数中,根据读取配置文件,判断是MODBUS_TCP,还是MODBUS_RTU来实例化不同的类。其中的 pModbus 指针定义如下:
CModbusS *pModbus; // modbus实例
最后创建两个线程,
一个应用层线程,应用线程可以和主线程在一起,也可以单独开一个线程
void * AppThread (void * lparam)
{
CModbusS * pModbuss = (CModbusS*)(lparam);
pModbuss->SetStopTag(FALSE);
pModbuss->MainControlS();
return 0;
}
一个链路层线程
void ProcessModbusSLinkThread(void pParam)
{
CModbusS pModbusLink = reinterpret_cast <CModbusS> (pParam);
return pModbusLink->LinkLayerMain();
}