PDA

View Full Version : Modbus implementation



ShamusVW
26th October 2009, 11:58
Hello everyone. New to this forum, loving using Qt.

My question:
I am trying to implement Modbus protocol using Qt on TCP. Looking at various articles, a lot of them are obviously for serial connection, and a lot more include precompiled libraries, and others are for linux (I'm using Windows), with the result that I'm not making progress.

However, from what I have seen, a socket connection gets made, with a few options set, using structures to store the ip address, port, file descriptor, etc which then uses the memset function to clear it. Then there is a command setsockopt to set options of the socket like TCP_NODELAY, IP_TOS. Do I really need all this?

In Qt, it "appears" as easy as


tcpSocket = new QTcpSocket(this);
tcpSocket->connectToHost("193.1.1.14",502);

Now I say appear, because I'm sure it isn't, however, I do manage to connect by viewing the socket state, and it returns connection established.

The next stage again appears that I need to write to the socket, and then read something back. Now this is where I am stuck, and would appreciate help. I don't mind the hard work, just some guidance please.

ShamusVW
27th October 2009, 03:02
Please, is there no one who has any suggestions/help for me? I really am desperate for some help, and I see my post slowly moving down the page, I don't want it to disappear!:(
Thanks.
Shaun

kuzulis
27th October 2009, 05:23
So what's the actual problem then? I do not understand!:)
Read assistant QAbstractSocket and see examples of in /examples/network

ShamusVW
2nd November 2009, 08:24
Hi
Thanks for replying.
My actual probelm is that I do not know what exactly to write to the socket, and how to set up the data structure. All the info I come across is written for Linux, and so it uses different libraries. I have been working through them for about 2 weeks now, no luck. The one bit of source I got using Qt, the serial connection had been done, but for TCP, it had said "to be done", i.e. not yet implemented. It was using the modbus library (but this is Linux again).
Also, it jumps everywhere between fuinctions, and it is too difficult for me to keep track of what is happening, even using breakpoints in Qt Creator.
I would love to do it using neat classes, but like I say, battling to understand it.

kuzulis
2nd November 2009, 10:06
1. Modbus TCP protocol itself is very easy to implement yourself (from scratch), it is not necessary to use any third-party libraries. Protocol is very easy.
2. The structure of the data before writing it must first be prepared, for example, QByteArray, and then this array is written in the socket, etc.
3. See the documentation for the protocol:
Modbus_Messaging_Implementation_Guide_V1_0b.pdf
Modbus_Application_Protocol_V1_1b.pdf

http://www.modbus.org/
http://en.wikipedia.org/wiki/Modbus

etc. everything there is clear written! Read the documentation! :)

ShamusVW
2nd November 2009, 10:23
Easy for some, I'm battling:confused:

This is what I have so far, perhaps you can comment?

I first make a connection to a button being pressed...


connect(getFortuneButton, SIGNAL(clicked()),this, SLOT(requestNewFortune()));

Then in my slot...


getFortuneButton->setEnabled(false);
blockSize = 0;
tcpSocket->abort();
tcpSocket->connectToHost("193.1.1.14",502);

QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << (quint16)0;
block.resize(7);
//************************************************** *********
//*****see http://www.simplymodbus.ca/TCP.htm ************
//************************************************** *********
block[0] = 0x0001; //unique transaction identifier
block[1] = 0x0000; //protocol identifier - always 00 00
block[2] = 0x0006; //message length i.e. 6 bytes to follow
block[3] = 0x01; //unit identifier
block[4] = 0x03; //the function code
block[5] = 0x0000; //40108-40001 = 107 = 6B
block[6] = 0x0064; //no. of registers requested 64(16) = 100(10)
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));

qDebug() << tcpSocket->write(block);

qDebug shows 7 bytes having been written to the socket. This should be 12 bytes?

Now I have a slot to catch the readyRead of the socket, but it never gets activated!

kuzulis
2nd November 2009, 10:44
In this situation, simply do not use QDataStream in general!

example:


getFortuneButton-> setEnabled (false);
blockSize = 0;
tcpSocket-> abort ();
tcpSocket-> connectToHost ( "193.1.1.14", 502);

QByteArray block;
/ / TID = 2 BYTE!!!
block [0] = 0x00; / / unique transaction identifier HI BYTE
block [1] = 0x01; / / unique transaction identifier LO BYTE
/ / PID = 2 BYTE!!!
block [2] = 0x00; / / protocol identifier HI
block [3] = 0x00; / / protocol identifier LO
/ / Length = 2 BYTE!!!
block [4] = 0x00; / / message length i.e. 6 bytes to follow HI
block [5] = 0x06; / / message length i.e. 6 bytes to follow LO
/ / UID = 1 BYTE!!!
block [6] = 0x01; / / unit identifier
/ / FC = 1 BYTE!!!
block [7] = 0x03; / / the function code
/ / START REGS ADDR = 2 BYTE (example 4x5 - 4x10)
block [8] = 0x00; / / HI byte
block [9] = 0x04; / / LO byte (0x0004 - is start addr 4x5)
/ / COUNT REGS = 2 BYTE!!! (example 6 regs)
block [10] = 0x00; / / HI Byte
block [11] = 0x06; / / LO Byte

qDebug () <<tcpSocket-> write (block);

Total length of the ADU = MBHap + PDU = 7 + 5 = 12 BYTES!!!

PS: Read the documentation on the Modbus! :)

ShamusVW
2nd November 2009, 11:03
:)Thank you, the slot now gets activated.
I have actually read the documentation. Before 2 weeks ago, I had never heard of Modbus in my life! So basically I've been trying to get my head around it. Also, I actually program in Delphi, so at the same time I am trying to learn C++/Qt as this is one of my modules I am doing.
Thank you for this, I have spent for ages trying to get the library working for me, but glad that it seems possible in such a simpler setup.

I will keep working on the program.

ShamusVW
2nd November 2009, 12:11
Thank you. In a readyRead slot, I have placed the following...


QByteArray block;
block = tcpSocket->readAll();

and then running in debug mode, I notice similarities between what is read to what I pick up in a program called "Simply Modbus TCP" (demo version), so this is my first positive result.:D

kuzulis
2nd November 2009, 13:17
No, I do not need to use readAll ()!!! (IMHO)

Algorithm reading about the following:
1. Must first read the first 7 bytes of incoming data packet (MBHAP)
2. To analyze these 7 bytes and allocate field Length
3. Read the remaining bytes from the socket size = Length - 1 (PDU)

ShamusVW
3rd November 2009, 05:46
I assume by MBHAP you mean the header?

From Modbus_Application_Protocol_V1_1b.pdf I get:
MBAP = 7 bytes, for TCP that leaves 253 bytes for PDU, giving total of 260 bytes for ADU.


The mb_rsp_pdu is defined as: (response from slave)
mb_rsp_pdu = {function_code, response_data}, where
function_code = [1 byte] MODBUS function code
response_data = [n bytes] This field is function code dependent and usually
contains information such as variable references,
variable counts, data offsets, sub-function codes, etc.

And then further in the document: (for function code 3)


Response
Function code 1 Byte 0x03
Byte count 1 Byte 2 x N*
Register value N* x 2 Bytes
*N = Quantity of Registers

Now byte count I am assuming is 2xN (qty of registers) since each register is 2 bytes long (16bits), but then that means only the first 2 bytes are used for letting me know how much data is following, i.e. byte #2, but previously it says 7 bytes? And that is why I just do a full readAll() call, even though not best way.

When I use (demo) "Simply Modbus TCP", it shows the registers I want are in numbers 40017 & 40018, with an offset of 40001, so this then means I want registers 16 & 17?

So I set up my data structure as :

block [0] = 0x00; // unique transaction identifier HI BYTE
block [1] = 0x01; // unique transaction identifier LO BYTE
block [2] = 0x00; // protocol identifier HI
block [3] = 0x00; // protocol identifier LO
block [4] = 0x00; // message length i.e. 6 bytes to follow HI
block [5] = 0x06; // message length i.e. 6 bytes to follow LO
block [6] = 0x01; // unit identifier
block [7] = 0x03; // the function code
block [8] = 0x00; // HI byte
block [9] = 0x10; // Start from register mem addr 16
block [10] = 0x00; // Hi byte
block [11] = 0x02; // Read in 2 registers

And the response I get is

block [0] = 0x00;
block [1] = 0x01;
block [2] = 0x00;
block [3] = 0x00;
block [4] = 0x00;
block [5] = 0x07;
block [6] = 0x01;
block [7] = 0x03;
block [8] = 0x04;
block [9] = 0x3D;
block [10] = 0x7E;
block [11] = 0xBF;
block [12] = 0x26;

So it is reading the correct registers, yet 13 bytes are returned, 4 making up the data, block[8] I take it tells me how many bytes are following, [7] is just a return of the function code, remainder back to 0 as in request, except for [5] that (I assume?) again tells me how many bytes are still to follow, i.e. 7, plust the 6 already given ([0]-[6]) = 13.

So I am still a bit confused as to its makeup. block [9] to [12] is the data I want, basically a float point number as single-precision, which is fine to convert.

Possibly one more question, if you aren't already tired of my ignorance, when I do a


qDebug() << QString::number(block[9],16);
qDebug() << QString::number(block[10],16);
qDebug() << QString::number(block[11],16);
qDebug() << QString::number(block[12],16);


it prints out


"3d"
"7e"
"ffffffffffffffbf"
"26"

Why does the 3rd value come out so strange, yet in the debugging (watchpoints) it displays as "bf"?

Again, thanks for your insight. I appreciate your time and help.
Shaun

ShamusVW
3rd November 2009, 06:49
I have subsequently found this in another document:


The header is 7 bytes long:
Transaction Identifier - It is used for transaction pairing, the MODBUS server copies in the
response the transaction identifier of the request.
Protocol Identifier – It is used for intra-system multiplexing. The MODBUS protocol is identified
by the value 0.
Length - The length field is a byte count of the following fields, including the Unit Identifier and data fields.
Unit Identifier – This field is used for intra-system routing purpose. It is typically used to
communicate to a MODBUS or a MODBUS+ serial line slave through a gateway between an

where
Transaction Identifier : Recopied by the server from the received request
Protocol Identifier : Recopied by the server from the received request
Length : Initialized by the server (Response - no. of following bytes)
Unit Identifier : Recopied by the server from the received request

so it is indeed 7 bytes.

So my response makes sense, until it then gets to block[7]-[12]. I can't seem to find reference to this, or more specifically, to block [7] & [8] of my response.

kuzulis
3rd November 2009, 06:54
Yuor response:


block [0] = 0x00; //TID HI
block [1] = 0x01; //TID LO
block [2] = 0x00; //PID HI
block [3] = 0x00; //PID LO
block [4] = 0x00; // LENGTH HI
block [5] = 0x07; // LENGTH LO
block [6] = 0x01; //UID

block [7] = 0x03; // FC
block [8] = 0x04; // Bytes Count
block [9] = 0x3D; //HI DATA REGS in addr 0x10
block [10] = 0x7E; //LO DATA REGS in addr 0x10
block [11] = 0xBF; //HI DATA REGS in addr 0x11
block [12] = 0x26; //LO DATA REGS in addr 0x11



All right!

MBHAP = [0] - [6]
PDU = [7] - [12]

block [4] and block [5] = length = 0x0007 bytes, ie [6] - [12] = 7 byte!
Parameter length - controls the length of the entire package, including [6] - [12]!

block [8] - includes the length of the data from the registers! because You asked for data from two registers to 16 bits - a natural response will be data = 2 x 16 bits = 4 bytes, ie [8] = 4 bytes!

Next come the data itself is already registers:
0x10 = [9] - [10] = 0x3D7E
0x11 = [11] - [12] = 0xBF26

I do not understand that you do not understand? I'll have to explain everything. Sam understood further.

PS:
Qt has nothing to do with. We first with Modbus protocol

ShamusVW
3rd November 2009, 08:51
Thank you, I have figured out how the response is made up your assistance.
Any idea on my question i.e.

qDebug() << QString::number(block[9],16);
qDebug() << QString::number(block[10],16);
qDebug() << QString::number(block[11],16);
qDebug() << QString::number(block[12],16);

it prints out

"3d"
"7e"
"ffffffffffffffbf"
"26"

Shaun

sharad_pise
21st December 2011, 10:32
Have got any solution to the 0xffffffbf problem?
I am facing the same problem while reading a float data from modbus .
If u have a solution pls tell me.