private:
Ui::Widget *ui;
QTcpServer *tcpServer;
QTcpSocket *clientConnection ;
QByteArray sendBuf;
QByteArray receBuf;
qint64 bytesRead ;//接收到的字节数
public:
/*****for modbus function*******/
void checkComm0Modbus(void);
void readCoil(void) ;
void forceSingleCoil(void);
void readRegisters();
void presetSingleRegister(void);
uint16 getCoilVal(uint16 addr,uint16 *tempData);
uint16 setCoilVal(uint16 addr,uint16 tempData);
uint16 getRegisterVal(uint16 addr,uint16 *tempData);
uint16 setRegisterVal(uint16 addr,uint16 tempData) ;
/******************************/
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = new QTcpServer(this);
//if(!tcpServer->listen(QHostAddress::LocalHost,6666))
if(!tcpServer->listen(QHostAddress("192.168.1.230"),502))
{ //监听本地主机的6666端口,如果出错就输出错误信息,并关闭
qDebug() << tcpServer->errorString();
close();
}
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::acceptConnection()//response the sever's connection request--
{
clientConnection = tcpServer->nextPendingConnection();
//我们获取已经建立的连接的子套接字
connect(clientConnection,SIGNAL(disconnected()),clientConnection,SLOT(deleteLater()));
connect(clientConnection,SIGNAL(readyRead()),this,SLOT(readData()));
connect(clientConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
ui->statusLabel->setText("already constuct clientConnection");
}
void Widget::readData(void)//when the readyRead signal is emit,this slots function is excuted
{
bytesRead=clientConnection->bytesAvailable();
if(bytesRead>=8){
DEBUG_BYSONG<<bytesRead;
receBuf = clientConnection->readAll();
DEBUG_BYSONG<<receBuf.toHex().toUpper();
checkComm0Modbus();//to parse mb
clientConnection->write(sendBuf);
bytesRead=0;
}
}
说明:
1.micro2440监听本地502端口。modbus之于502就像ftp之于21一样。当有客户端连接请求时激发newConnection()信号从而执行slots acceptConnection()。
2.在acceptConnection()里面应答连接,并建立readyRead()信号与 readData()槽函数的连接。readyRead信号在端口有数据到达时发射。
3.在readData ()函数里面读取数据(一个modbus frame)。调用checkComm0Modbus()分析此帧。
4.modbus tcp的帧结构相比串口modbus rtu的前面多了6个字节modbus协议头即mbap header,后面少了2个字节的crc,中间部分除了第一字节的node其余字节完全相同,都遵守modbus帧规范。
/***************************************************implement modbus function**************************/
void Widget::checkComm0Modbus(void)
{
uint16 tempData=0;
sendBuf.resize(0);
sendBuf[0]=receBuf[0];
sendBuf[1]=receBuf[1];
sendBuf[2]=receBuf[2];
sendBuf[3]=receBuf[3];
//receBuf[0];//
//receBuf[1];//
//receBuf[2];//00
//receBuf[3];//00
//receBuf[4];//length mb hi byte
//receBuf[5];//length mb lo byte 6
//receBuf[6];//ff
//receBuf[7];//function code
//receBuf[8];//address hi byte
//receBuf[9];//address lo byte
//receBuf[10];//length hi byte
//receBuf[11];//length lo byte
//sendBuf[0];//**
//sendBuf[1];//**
//sendBuf[2];//**
//sendBuf[3];//**
//sendBuf[4];//length mb hi byte
//sendBuf[5];//length mb lo byte 11
//sendBuf[6];//**
//sendBuf[7];//**
//sendBuf[8];//length below 8
//sendBuf[9];//data1 hi byte
//sendBuf[10];//data1 lo byte
//sendBuf[11];//data2 hi byte
//sendBuf[12];//data2 lo byte
//sendBuf[13];//data3 hi byte
//sendBuf[14];//data3 lo byte
//sendBuf[15];//data4 hi byte
//sendBuf[16];//data4 lo byte
if(bytesRead >= 12)
{
if(receBuf[0]==receBuf[0])
{
//if(crcData == receBuf[7]+(receBuf[6]<<8))
{
if(receBuf[7] == (char)1)
{
readCoil();
}
else if(receBuf[7] == (char)2)
{
readCoil();
}
else if(receBuf[7] == (char)3)
{
readRegisters();
}
else if(receBuf[7] == (char)4)
{
readRegisters();
}
else if(receBuf[7] == (char)5)
{
forceSingleCoil();
}
else if(receBuf[7] == (char)6)
{
presetSingleRegister();
}
}
}
}
}
void Widget::readCoil(void)
{
DEBUG_BYSONG<<"readCoil";
/*
10 00 00 00 00 06 ff 02 00 00 00 03 //客户端(pc)请求帧。
10 00 00 00 00 04 FF 02 01 01 //服务器端(micro2440)回复帧
10 00 00 00 00 06 ff 02 00 00 00 03
10 00 00 00 00 06 mbap header
ff 地址
02 功能号
00 00 start address
00 03 length
10 00 00 00 00 04 FF 02 01 01
10 00 00 00 00 04 mbap header
ff 地址
02 功能号
01 length of the follows
01 data即0000 0001b.所以adress0:1 address1:0 address2:0
*/
uint8 addr;
uint8 tempAddr;
uint16 byteCount;
uint16 bitCount;
uint16 crcData;
uint8 position;
uint8 i,k;
//uint8 result;
uint16 tempData;
uint8 temp;
uint8 exit = 0;
addr = (receBuf[2+6]<<8) + receBuf[3+6];
tempAddr = addr & 0xff;
bitCount = (receBuf[4+6]<<8) + receBuf[5+6];
bitCount &= 0xff;
byteCount = bitCount / 8;
if(bitCount%8 != 0)
byteCount++;
for(k=0;k<byteCount;k++)
{
position = k + 3 + 6;
sendBuf[position] = 0;
temp=0;
for(i=0;i<8;i++)
{
getCoilVal(tempAddr,&tempData);
temp|=tempData << i;
sendBuf[position] =temp;
tempAddr++;
if(tempAddr >= addr+bitCount)
{
exit = 1;
break;
}
}
if(exit == 1)
break;
}
sendBuf[4]=(byteCount+(char)3)>>8;//length mb hi byte
sendBuf[5]=(byteCount+(char)3) & 0xff;//length mb lo byte 11
sendBuf[0+6] = receBuf[6];
sendBuf[1+6] = receBuf[7];
sendBuf[2+6] = byteCount;
}//void readCoil(void)
void Widget::readRegisters(void)
{
/*
00 06 00 00 00 06 ff 03 00 00 00 03 //客户端(pc)请求帧。
00 06 00 00 00 09 FF 03 06 00 00 00 01 00 02 //服务器端(micro2440)回复帧
其中
00 06 00 00 00 06 ff 03 00 00 00 03
前6字节是mbap header:
00 事务处理标识符hi
06 事务处理标识符lo。一般主机每发送一个modbus帧,事务处理标识符+1
00 00 modbus协议标识符 必须为 00 00
00 信文长度hi
06 信文长度lo。指后续的字节数。
后面的字节属于正文
ff modbus 节点地址,基于tcp/ip的modbus此地址无用。因为ip+port就可以唯一确定是哪个服务器
03 function code
00 start address hi
00 start address lo
00 length hi
03 length lo
00 06 00 00 00 09 FF 03 06 00 00 00 01 00 02
前6字节是mbap header:
00 06 00 00 前4字节和客户端发来的一样。
00 信文长度hi
09 信文长度lo。指后续的字节数。
后面的字节属于正文
ff modbus 节点地址。和客户端发来的一样。
03 function code。和客户端发来的一样。
06 length of the follows
00 00 data1
00 01 data2
00 02 data3
*/
DEBUG_BYSONG<<"readRegisters";
uint8 addr;
uint8 tempAddr;
uint16 crcData;
uint16 readCount;
uint16 byteCount;
uint16 i;
uint16 tempData = 0;
addr = (receBuf[2+6]<<8) + receBuf[3+6];
tempAddr = addr & 0xff;
readCount = (receBuf[4+6]<<8) + receBuf[5+6];
if (readCount>166) readCount=166;/constraint the quantities
byteCount = readCount * 2;
for(i=0;i<byteCount;i+=2,tempAddr++)
{
getRegisterVal(tempAddr,&tempData);
sendBuf[i+3+6] = tempData >> 8;
sendBuf[i+4+6] = tempData & 0xff;
}
sendBuf[4]=(byteCount+(char)3)>>8;//length mb hi byte
sendBuf[5]=(byteCount+(char)3) & 0xff;//length mb lo byte 11
sendBuf[0+6] = receBuf[6];
sendBuf[1+6] = receBuf[7];
sendBuf[2+6] = byteCount;
// sendBuf[byteCount+6]='\0';
}//void readRegisters(void)
void Widget::forceSingleCoil(void)
{
/*
00 06 00 00 00 06 ff 05 00 03 FF 00 //client send mb frame。server response with the same
00 06 00 00 00 06 smap header
ff modbus node address.useless
05 function code
00 03 start address
ff 00 stand for 1。if clr 0,will be 00 00
*/
uint8 addr;
uint8 tempAddr;
uint16 tempData=0xabcd;
uint8 onOff;
uint8 i;
addr = (receBuf[2+6]<<8) + receBuf[3+6];
tempAddr = addr & 0xff;//°????è????±????·
onOff = (receBuf[4+6]<<8) + receBuf[5+6];
if(onOff == 0xff00)
{ //?è??ON
tempData = 1;
}
else if(onOff == 0x0000)
{ //?è??OFF
tempData = 0;
}
if (tempData==1 || tempData==0){
setCoilVal(tempAddr,tempData);
for(i=0;i<bytesRead;i++)
{
sendBuf[i] = receBuf[i];
}
}
}//void forceSingleCoil(void)
void Widget::presetSingleRegister(void)
{
/*
00 06 00 00 00 06 ff 06 00 03 FF 00 //client send mb frame。server response with the same
00 06 00 00 00 06 smap header
ff modbus node address.useless
06 function code
00 03 start address
12 66 the data to write into the address above
*/
uint8 addr;
uint8 tempAddr;
uint16 crcData;
uint16 tempData;
int i;
addr = (receBuf[2+6]<<8) + receBuf[3+6];
tempAddr = addr & 0xff;//°????è????±????·
tempData = (receBuf[4+6]<<8) + receBuf[5+6];
setRegisterVal(tempAddr,tempData);
for(i=0;i<bytesRead;i++)
{
sendBuf[i] = receBuf[i];
}
}
5.this is the most simple demo which implements the modbus tcp protocol.还需要根据mosbus规范完善异常情况的控制。同一时间仅支持一个客户连接请求。另一客户请求时,会把请一个踢掉,而服务最新的这个客户,可以使用多线程完善一下,使每个线程服务一个客户连接。。
6.使用UartAssist测试
function code 01
function code 03
7.使用modscan32测试,
8.使用dasserver测试
安装dasmbtcp,添加主题名 tcp2
在intouch中添加访问名access_tcp2
建立io整型点,test1.注意item是400003.不是40003.
可以看到在为地址400003采集数据时,dasmbtcp发出的请求帧是 00 B3 00 00 00 06 FF 03 00 02 00 01 。最后6个字节中,03功能号。符合mb规范。
当更改这个变量例如改成9,如图,的时候
dasmbtcp会发出请求帧,如下图,
即 01 42 00 00 00 09 FF 10 00 00 02 00 01 02 00 09 .最后9个字节中,0x10功能号。00 02 starting address。00 01 寄存器个数。02 后续字节数2个。00 09写入值。
16 (10 Hex) Preset Multiple Registers
下面是0x10号功能请求帧的格式
服务器需要响应帧类似如下:
再实验一个地址000006
很明显,function code 01。
当我们试图去置test2为off,如图,的时候,
dasmbtcp会发出请求帧,如下图,
是功能号0x0F
15 (0F Hex) Force Multiple Coils
由此确定:开发一个在支持wonderware dasmb 读和写的modbus设备,需要该设备实现01 02 03 04 0F 10功能号。而05 06置单线圈和单寄存器功能可以不要。因为wonderware dasmb置线圈和寄存器都是使用的0F 和10功能号,没有用到05 06。
10不必开放,在intouch里面修改4xxxx的变量时(function code 03),dasmb 自动发出10请求帧。
0F不必开放,在intouch里面修改0xxxx的变量时(function code 01),dasmb 自动发出0F请求帧。
本例没实现0x10号功能,所以改不了。按照mb规范加上这个功能很简单的,自己动动脑子啦。
http://download.csdn.net/detail/songqqnew/3863154