Skip to content

Commit 808aa7a

Browse files
committed
Queue optimization
Only one scan request allowed in queue. Only one request for slaves with "nonresponding" status.
1 parent 7a66536 commit 808aa7a

File tree

5 files changed

+126
-75
lines changed

5 files changed

+126
-75
lines changed

arduino-modbus-rtu-tcp-gateway/01-interfaces.ino

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,25 @@ ISR(WDT_vect) {
253253
#endif
254254

255255
#endif
256+
257+
258+
259+
260+
261+
#ifdef __arm__
262+
// should use uinstd.h to define sbrk but Due causes a conflict
263+
extern "C" char* sbrk(int incr);
264+
#else // __ARM__
265+
extern char *__brkval;
266+
#endif // __arm__
267+
268+
int freeMemory() {
269+
char top;
270+
#ifdef __arm__
271+
return &top - reinterpret_cast<char*>(sbrk(0));
272+
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
273+
return &top - __brkval;
274+
#else // __arm__
275+
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
276+
#endif // __arm__
277+
}

arduino-modbus-rtu-tcp-gateway/02-modbus-tcp.ino

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,41 @@
2929
#define ADDRESS_POS (6 * !localConfig.enableRtuOverTcp) // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
3030

3131
enum status : byte {
32-
STAT_OK,
33-
STAT_ERROR_0X,
34-
STAT_ERROR_0A,
35-
STAT_ERROR_0B,
36-
STAT_NUM // Number of status flags in this enum. Must be the last element within this enum!!
32+
STAT_OK, // Slave Responded
33+
STAT_ERROR_0X, // Slave Responded with Error (Codes 1~8)
34+
STAT_ERROR_0A, // Gateway Overloaded (Code 10)
35+
STAT_ERROR_0B, // Slave Failed to Respond (Code 11)
36+
STAT_ERROR_0B_QUEUE, // Slave Failed to Respond (Code 11) + in Queue
37+
STAT_NUM // Number of status flags in this enum. Must be the last element within this enum!!
3738
};
3839

3940
// bool arrays for storing Modbus RTU status of individual slaves
4041
uint8_t stat[STAT_NUM][(maxSlaves + 1 + 7) / 8];
4142

43+
// Scan request is in queue
44+
bool scanInQueue = false;
45+
4246
// array for storing error counts
4347
uint16_t errorCount[STAT_NUM];
4448
uint16_t errorInvalid;
4549

46-
// bool arrays for storing Modbus RTU status (responging or not responding). Array index corresponds to slave address.
47-
// uint8_t statOk[(maxSlaves + 1 + 7) / 8];
48-
// uint8_t statError0B[(maxSlaves + 1 + 7) / 8];
50+
uint16_t queueDataSize;
51+
uint8_t queueHeadersSize;
4952

5053
uint8_t masks[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
5154

5255
typedef struct {
5356
byte tid[2]; // MBAP Transaction ID
54-
byte msgLen; // lenght of Modbus message stored in queuePDUs
57+
byte msgLen; // lenght of Modbus message stored in queueData
5558
IPAddress remIP; // remote IP for UDP client (UDP response is sent back to remote IP)
5659
unsigned int remPort; // remote port for UDP client (UDP response is sent back to remote port)
5760
byte clientNum; // TCP client who sent the request, UDP_REQUEST (0xFF) designates UDP client
5861
byte atts; // attempts counter
5962
} header;
6063

6164
// each request is stored in 3 queues (all queues are written to, read and deleted in sync)
62-
CircularBuffer<header, reqQueueCount> queueHeaders; // queue of requests' headers and metadata (MBAP transaction ID, MBAP unit ID, PDU length, remIP, remPort, TCP client)
63-
CircularBuffer<byte, reqQueueSize> queuePDUs; // queue of PDU data (function code, data)
65+
CircularBuffer<header, maxQueueRequests> queueHeaders; // queue of requests' headers and metadata (MBAP transaction ID, MBAP unit ID, PDU length, remIP, remPort, TCP client)
66+
CircularBuffer<byte, maxQueueData> queueData; // queue of PDU data (function code, data)
6467

6568
void recvUdp() {
6669
unsigned int msgLength = Udp.parsePacket();
@@ -81,13 +84,14 @@ void recvUdp() {
8184
Udp.write(inBuffer, 5);
8285
Udp.write(0x03);
8386
}
84-
Udp.write(inBuffer[ADDRESS_POS]); // address
85-
Udp.write(inBuffer[ADDRESS_POS + 1] + 0x80); // function + 0x80
87+
byte addressPos = 6 * !localConfig.enableRtuOverTcp; // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
88+
Udp.write(inBuffer[addressPos]); // address
89+
Udp.write(inBuffer[addressPos + 1] + 0x80); // function + 0x80
8690
Udp.write(errorCode);
8791
if (localConfig.enableRtuOverTcp) {
8892
crc = 0xFFFF;
89-
calculateCRC(inBuffer[ADDRESS_POS]);
90-
calculateCRC(inBuffer[ADDRESS_POS + 1] + 0x80);
93+
calculateCRC(inBuffer[addressPos]);
94+
calculateCRC(inBuffer[addressPos + 1] + 0x80);
9195
calculateCRC(errorCode);
9296
Udp.write(lowByte(crc)); // send CRC, low byte first
9397
Udp.write(highByte(crc));
@@ -120,13 +124,14 @@ void recvTcp() {
120124
client.write(inBuffer, 5);
121125
client.write(0x03);
122126
}
123-
client.write(inBuffer[ADDRESS_POS]); // address
124-
client.write(inBuffer[ADDRESS_POS + 1] + 0x80); // function + 0x80
127+
byte addressPos = 6 * !localConfig.enableRtuOverTcp; // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
128+
client.write(inBuffer[addressPos]); // address
129+
client.write(inBuffer[addressPos + 1] + 0x80); // function + 0x80
125130
client.write(errorCode);
126131
if (localConfig.enableRtuOverTcp) {
127132
crc = 0xFFFF;
128-
calculateCRC(inBuffer[ADDRESS_POS]);
129-
calculateCRC(inBuffer[ADDRESS_POS + 1] + 0x80);
133+
calculateCRC(inBuffer[addressPos]);
134+
calculateCRC(inBuffer[addressPos + 1] + 0x80);
130135
calculateCRC(errorCode);
131136
client.write(lowByte(crc)); // send CRC, low byte first
132137
client.write(highByte(crc));
@@ -140,8 +145,9 @@ void recvTcp() {
140145
}
141146

142147
void processRequests() {
143-
// Insert scan request into queue
144-
if (scanCounter != 0 && queueHeaders.available() > 1 && queuePDUs.available() > sizeof(scanCommand) + 1) {
148+
// Insert scan request into queue, allow only one scan request in a queue
149+
if (scanCounter != 0 && queueHeaders.available() > 1 && queueData.available() > sizeof(scanCommand) + 1 && scanInQueue == false) {
150+
scanInQueue = true;
145151
// Store scan request in request queue
146152
queueHeaders.push(header{
147153
{ 0x00, 0x00 }, // tid[2]
@@ -151,9 +157,9 @@ void processRequests() {
151157
SCAN_REQUEST, // clientNum
152158
localConfig.serialAttempts - 1, // atts
153159
});
154-
queuePDUs.push(scanCounter); // address of the scanned slave
160+
queueData.push(scanCounter); // address of the scanned slave
155161
for (byte i = 0; i < sizeof(scanCommand); i++) {
156-
queuePDUs.push(scanCommand[i]);
162+
queueData.push(scanCommand[i]);
157163
}
158164
scanCounter++;
159165
if (scanCounter == maxSlaves + 1) scanCounter = 0;
@@ -176,23 +182,32 @@ void processRequests() {
176182
}
177183

178184
byte checkRequest(const byte inBuffer[], unsigned int msgLength, const IPAddress remoteIP, const unsigned int remotePort, const byte clientNum) {
179-
if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
185+
byte addressPos = 6 * !localConfig.enableRtuOverTcp; // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
186+
if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
180187
if (checkCRC(inBuffer, msgLength) == false) {
181188
errorInvalid++;
182-
return 0; // reject: do nothing and return no error code
189+
return 0; // drop request and do not return an code
183190
}
184191
} else { // check MBAP header structure for Modbus TCP/UDP
185192
if (inBuffer[2] != 0x00 || inBuffer[3] != 0x00 || inBuffer[4] != 0x00 || inBuffer[5] != msgLength - 6) {
186193
errorInvalid++;
187-
return 0; // reject: do nothing and return no error code
194+
return 0; // drop request and do not return an code
188195
}
189196
}
190-
msgLength = msgLength - ADDRESS_POS - (2 * localConfig.enableRtuOverTcp); // in Modbus RTU over TCP/UDP do not store CRC
197+
msgLength = msgLength - addressPos - (2 * localConfig.enableRtuOverTcp); // in Modbus RTU over TCP/UDP do not store CRC
191198
// check if we have space in request queue
192-
if (queueHeaders.available() < 1 || queuePDUs.available() < msgLength) {
193-
setSlaveStatus(inBuffer[ADDRESS_POS], STAT_ERROR_0A, true);
199+
if (queueHeaders.available() < 1 || queueData.available() < msgLength) {
200+
setSlaveStatus(inBuffer[addressPos], STAT_ERROR_0A, true);
194201
return 0x0A; // return modbus error 0x0A (Gateway Overloaded)
195202
}
203+
// allow only one request to non responding slaves
204+
if (getSlaveStatus(inBuffer[addressPos], STAT_ERROR_0B_QUEUE)) {
205+
setSlaveStatus(inBuffer[addressPos], STAT_ERROR_0B, true);
206+
return 0x0B; // return modbus error 11 (Gateway Target Device Failed to Respond) - usually means that target device (address) is not present
207+
} else if (getSlaveStatus(inBuffer[addressPos], STAT_ERROR_0B)) {
208+
setSlaveStatus(inBuffer[addressPos], STAT_ERROR_0B_QUEUE, true);
209+
}
210+
196211
// all checkes passed OK, we can store the incoming data in request queue
197212
// Store in request queue
198213
queueHeaders.push(header{
@@ -204,15 +219,18 @@ byte checkRequest(const byte inBuffer[], unsigned int msgLength, const IPAddress
204219
0, // atts
205220
});
206221
for (byte i = 0; i < msgLength; i++) {
207-
queuePDUs.push(inBuffer[i + ADDRESS_POS]);
222+
queueData.push(inBuffer[i + addressPos]);
208223
}
224+
if (queueData.size() > queueDataSize) queueDataSize = queueData.size();
225+
if (queueHeaders.size() > queueHeadersSize) queueHeadersSize = queueHeaders.size();
209226
return 0;
210227
}
211228

212229
void deleteRequest() // delete request from queue
213230
{
231+
if (queueHeaders.first().clientNum == SCAN_REQUEST) scanInQueue = false;
214232
for (byte i = 0; i < queueHeaders.first().msgLen; i++) {
215-
queuePDUs.shift();
233+
queueData.shift();
216234
}
217235
queueHeaders.shift();
218236
}

arduino-modbus-rtu-tcp-gateway/03-modbus-rtu.ino

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ void sendSerial() {
3434
crc = 0xFFFF;
3535
}
3636
while (mySerial.availableForWrite() > 0 && txNdx < myHeader.msgLen) {
37-
mySerial.write(queuePDUs[txNdx]);
38-
calculateCRC(queuePDUs[txNdx]);
37+
mySerial.write(queueData[txNdx]);
38+
calculateCRC(queueData[txNdx]);
3939
txNdx++;
4040
}
4141
if (mySerial.availableForWrite() > 1 && txNdx == myHeader.msgLen) {
@@ -60,7 +60,7 @@ void sendSerial() {
6060
#ifdef RS485_CONTROL_PIN
6161
digitalWrite(RS485_CONTROL_PIN, RS485_RECEIVE); // Disable RS485 Transmit
6262
#endif /* RS485_CONTROL_PIN */
63-
if (queuePDUs[0] == 0x00) { // Modbus broadcast - we do not count attempts and delete immediatelly
63+
if (queueData[0] == 0x00) { // Modbus broadcast - we do not count attempts and delete immediatelly
6464
serialState = IDLE;
6565
deleteRequest();
6666
} else {
@@ -93,7 +93,7 @@ void recvSerial() {
9393
// Process Serial data
9494
// Checks: 1) RTU frame is without errors; 2) CRC; 3) address of incoming packet against first request in queue; 4) only expected responses are forwarded to TCP/UDP
9595
header myHeader = queueHeaders.first();
96-
if (!rxErr && checkCRC(serialIn, rxNdx) == true && serialIn[0] == queuePDUs[0] && serialState == WAITING) {
96+
if (!rxErr && checkCRC(serialIn, rxNdx) == true && serialIn[0] == queueData[0] && serialState == WAITING) {
9797
if (serialIn[1] > 0x80 && myHeader.clientNum != SCAN_REQUEST) {
9898
setSlaveStatus(serialIn[0], STAT_ERROR_0X, true);
9999
} else {
@@ -139,19 +139,18 @@ void recvSerial() {
139139
// Deal with Serial timeouts (i.e. Modbus RTU timeouts)
140140
if (serialState == WAITING && requestTimeout.isOver()) {
141141
header myHeader = queueHeaders.first();
142+
if (myHeader.clientNum != SCAN_REQUEST) setSlaveStatus(queueData[0], STAT_ERROR_0B_QUEUE, true);
142143
if (myHeader.atts >= localConfig.serialAttempts) {
143144
if (myHeader.clientNum != SCAN_REQUEST) {
144145
// send modbus error 0x0B (Gateway Target Device Failed to Respond) - usually means that target device (address) is not present
145-
setSlaveStatus(queuePDUs[0], STAT_ERROR_0B, true);
146+
setSlaveStatus(queueData[0], STAT_ERROR_0B, true);
146147
byte MBAP[] = { myHeader.tid[0], myHeader.tid[1], 0x00, 0x00, 0x00, 0x03 };
147-
byte PDU[] = { queuePDUs[0], (byte)(queuePDUs[1] + 0x80), 0x0B };
148+
byte PDU[] = { queueData[0], (byte)(queueData[1] + 0x80), 0x0B };
148149
crc = 0xFFFF;
149150
for (byte i = 0; i < sizeof(PDU); i++) {
150151
calculateCRC(PDU[i]);
151152
}
152153
switch (myHeader.clientNum) {
153-
case SCAN_REQUEST:
154-
break;
155154
case UDP_REQUEST:
156155
Udp.beginPacket(myHeader.remIP, myHeader.remPort);
157156
if (!localConfig.enableRtuOverTcp) {
@@ -192,7 +191,7 @@ void recvSerial() {
192191
deleteRequest();
193192
} // if (myHeader.atts >= MAX_RETRY)
194193
serialState = IDLE;
195-
} // if (requestTimeout.isOver() && expectingData == true)
194+
} // if (serialState == WAITING && requestTimeout.isOver())
196195
}
197196

198197
bool checkCRC(byte buf[], int len) {

arduino-modbus-rtu-tcp-gateway/05-pages.ino

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- renders the "please wait" message instead of the content (= request page number 0xFF, will be forwarded to home page after 5 seconds)
1717
1818
helperInput
19+
helperStats
1920
- renders some repetitive HTML code for inputs
2021
2122
send404, send204
@@ -161,6 +162,7 @@ void contentStatus(ChunkedPrint &chunked) {
161162
chunked.print(version[1], HEX);
162163
chunked.print(F("<tr><td>Microcontroller:<td>"));
163164
chunked.print(BOARD);
165+
// chunked.print(freeMemory());
164166
chunked.print(F("<tr><td>Ethernet Chip:<td>"));
165167
switch (Ethernet.hardwareStatus()) {
166168
case EthernetW5100:
@@ -200,8 +202,19 @@ void contentStatus(ChunkedPrint &chunked) {
200202

201203
chunked.print(F("<tr><td>IP Address:<td>"));
202204
chunked.print(Ethernet.localIP());
205+
chunked.print(F("<tr><td>Requests Queue:<td>"));
206+
chunked.print(queueDataSize);
207+
chunked.print(F(" / "));
208+
chunked.print(maxQueueData);
209+
chunked.print(F(" bytes, "));
210+
chunked.print(queueHeadersSize);
211+
chunked.print(F(" / "));
212+
chunked.print(maxQueueRequests);
213+
chunked.print(F(" requests"));
214+
queueDataSize = 0;
215+
queueHeadersSize = 0;
203216

204-
#ifdef ENABLE_EXTRA_DIAG
217+
#ifdef ENABLE_EXTRA_DIAG
205218
chunked.print(F("<tr><td>Run Time:<td>"));
206219
byte mod_seconds = byte((seconds) % 60);
207220
byte mod_minutes = byte((seconds / 60) % 60);
@@ -323,21 +336,15 @@ void contentStatus(ChunkedPrint &chunked) {
323336

324337
chunked.print(F("<tr><td><br>"
325338
"<tr><td>Modbus Stats:<td>"));
326-
chunked.print(errorCount[STAT_OK]);
327-
chunked.print(F(" Slave Responded"
328-
"<tr><td><td>"));
329-
chunked.print(errorCount[STAT_ERROR_0X]);
330-
chunked.print(F(" Slave Responded with Error (Codes 1~8)"
331-
"<tr><td><td>"));
332-
chunked.print(errorCount[STAT_ERROR_0A]);
333-
chunked.print(F(" Gateway Overloaded (Code 10)"
334-
"<tr><td><td>"));
335-
chunked.print(errorCount[STAT_ERROR_0B]);
336-
chunked.print(F(" Slave Failed to Respond (Code 11)"
337-
"<tr><td><td>"));
339+
for (byte i = 0; i < STAT_NUM; i++) {
340+
if (i == STAT_ERROR_0B_QUEUE) continue;
341+
chunked.print(errorCount[i]);
342+
helperStats(chunked, i);
343+
chunked.print(F("<tr><td><td>"));
344+
}
338345
chunked.print(errorInvalid);
339-
chunked.print(F(" Invalid Request (Dropped by Gateway)"));
340-
chunked.print(F("<tr><td><br>"
346+
chunked.print(F(" Invalid Request (Dropped by Gateway)"
347+
"<tr><td><br>"
341348
"<tr><td>Modbus TCP/UDP Masters:"));
342349
byte countMasters = 0;
343350
for (byte i = 0; i < maxSockNum; i++) {
@@ -374,22 +381,7 @@ void contentStatus(ChunkedPrint &chunked) {
374381
chunked.print(F(" Scanning..."));
375382
break;
376383
}
377-
switch (s) {
378-
case STAT_OK:
379-
chunked.print(F(" Slave Responded"));
380-
break;
381-
case STAT_ERROR_0X:
382-
chunked.print(F(" Slave Responded with Error (Codes 1~8)"));
383-
break;
384-
case STAT_ERROR_0A:
385-
chunked.print(F(" Gateway Overloaded (Code 10)"));
386-
break;
387-
case STAT_ERROR_0B:
388-
chunked.print(F(" Slave Failed to Respond (Code 11)"));
389-
break;
390-
default:
391-
break;
392-
}
384+
helperStats(chunked, s);
393385
}
394386
}
395387
}
@@ -616,6 +608,26 @@ void helperInput(ChunkedPrint &chunked) {
616608
chunked.print(F("<td><input size=7 required type=number name="));
617609
}
618610

611+
void helperStats(ChunkedPrint &chunked, const byte stat) {
612+
switch (stat) {
613+
case STAT_OK:
614+
chunked.print(F(" Slave Responded"));
615+
break;
616+
case STAT_ERROR_0X:
617+
chunked.print(F(" Slave Responded with Error (Codes 1~8)"));
618+
break;
619+
case STAT_ERROR_0A:
620+
chunked.print(F(" Gateway Overloaded (Code 10)"));
621+
break;
622+
case STAT_ERROR_0B:
623+
case STAT_ERROR_0B_QUEUE:
624+
chunked.print(F(" Slave Failed to Respond (Code 11)"));
625+
break;
626+
default:
627+
break;
628+
}
629+
}
630+
619631
void send404(EthernetClient &client) {
620632
dbgln(F("[web out] response 404 file not found"));
621633
client.println(F("HTTP/1.1 404 Not Found\r\n"

0 commit comments

Comments
 (0)