Skip to content

Commit bc016fb

Browse files
committed
EEPROM health monitor, code optimization
1 parent 620c813 commit bc016fb

File tree

7 files changed

+173
-133
lines changed

7 files changed

+173
-133
lines changed

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
3030
- send Modbus request and recieve Modbus response
3131
- scan Modbus slaves on RS485 interface
3232
- queue (buffer) status
33-
- error counters (stored in EEPROM)
33+
- counters ("RTU Data", "Ethernet Data", "Modbus Statistics") are periodically saved to EEPROM (every 6 hours)
34+
- unsigned longs are used, rollover of counters is synchronized
3435
- content of the Modbus Status page is updated in the background (fetch API), javascript alert is shown if connection is lost
3536
* optimized TCP socket management (web interface and Modbus TCP):
3637
- gateway always listens for new web and Modbus TCP connections
@@ -57,6 +58,10 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
5758

5859
**Reboot**.
5960

61+
**EEPROM Health**. Keeps track of EEPROM write cycles (persistent, never cleared during factory resets)
62+
63+
**Ethernet Sockets**. Max number of usable sockets. See Limitations bellow. One socket is reserved for Modbus UDP, remaining sockets are shared between Modbus TCP and WebUI.
64+
6065
**Generate New MAC**. Generate new MAC address. First 3 bytes are fixed 90:A2:DA, remaining 3 bytes are true random.
6166

6267
<img src="/pics/modbus2.png" alt="02" style="zoom:100%;" />
@@ -73,7 +78,7 @@ Change settings of your Arduino-based Modbus RTU to Modbus TCP/UDP gateway via w
7378

7479
**Requests Queue**. Monitors internal request queue (buffer). The limits for bytes and for the number of requests stored in the queue can be configured in advanced settings.
7580

76-
**Modbus Statistics**. Counters for various errors. Counters are periodically saved to EEPROM. Insigned longs are used, rollover of counters is synchronized:
81+
**Modbus Statistics**.
7782
* **Slave Responded**. Slave responded with a valid Modbus RTU response within response timeout.
7883
* **Slave Responded with Error (Codes 1~8)**. Slave responded, but with an error. For the list of error codes see https://en.wikipedia.org/wiki/Modbus#Exception_responses.
7984
* **Gateway Overloaded (Code 10)**. Request queue is full (either the number of bytes stored or the number of requests stored). Request was dropped and the gateway responded with an error code 10.
@@ -148,14 +153,15 @@ Get the hardware (cheap clones from China are sufficient) and connect together:
148153
* **Arduino Nano, Uno or Mega** (and possibly other). On Mega you have to configure Serial in advanced settings in the sketch.
149154
* **W5100, W5200 or W5500 based Ethernet shield**. The ubiquitous W5100 shield for Uno/Mega is sufficient. If available, I recommend W5500 Ethernet Shield. !!! ENC28J60 will not work !!!
150155
* **TTL to RS485 module**:
151-
- with hardware automatic flow control (recommended, available on Aliexpress)<br>
152-
Arduino <-> Module<br>
153-
Tx1 <-> Tx<br>
154-
Rx0 <-> Rx
155-
- with flow controlled by pin (such as MAX485 module)<br>
156-
Arduino <-> MAX485<br>
157-
Tx1 <-> DI<br>
158-
Rx0 <-> RO<br>
156+
- recommended: TTL to RS485 module with hardware automatic flow control (available on Aliexpress)<br>
157+
Arduino <-> Module
158+
Tx1 <-> Tx
159+
Rx0 <-> Rx<br>
160+
161+
- not recommended: MAX485 module with flow controlled by pin (works but is vulnerable to burn outs)<br>
162+
Arduino <-> MAX485
163+
Tx1 <-> DI
164+
Rx0 <-> RO
159165
Pin 6 <-> DE,RE
160166

161167
Here is my setup:

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

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,29 @@ void startSerial() {
5454
#endif /* RS485_CONTROL_PIN */
5555
}
5656

57-
// time to send 1 character over serial in microseconds
58-
unsigned long charTime() {
59-
byte bits = // number of bits per character (11 in default Modbus RTU settings)
57+
// number of bits per character (11 in default Modbus RTU settings)
58+
byte bitsPerChar() {
59+
byte bits =
6060
1 + // start bit
6161
(((localConfig.serialConfig & 0x06) >> 1) + 5) + // data bits
6262
(((localConfig.serialConfig & 0x08) >> 3) + 1); // stop bits
6363
if (((localConfig.serialConfig & 0x30) >> 4) > 1) bits += 1; // parity bit (if present)
64-
return (bits * 10000UL) / (unsigned long)localConfig.baud;
64+
return bits;
6565
}
6666

67-
// character timeout
67+
// character timeout in micros
6868
unsigned long charTimeOut() {
6969
if (localConfig.baud <= 192) {
70-
return 1.5 * charTime(); // inter-character time-out should be 1,5T
70+
return (15000UL * bitsPerChar()) / localConfig.baud; // inter-character time-out should be 1,5T
7171
} else {
7272
return 750;
7373
}
7474
}
7575

76-
// minimum frame delay
76+
// minimum frame delay in micros
7777
unsigned long frameDelay() {
7878
if (localConfig.baud <= 192) {
79-
return 3.5 * charTime(); // inter-frame delay should be 3,5T
79+
return (35000UL * bitsPerChar()) / localConfig.baud; // inter-frame delay should be 3,5T
8080
} else {
8181
return 1750; // 1750 μs
8282
}
@@ -94,11 +94,11 @@ void startEthernet() {
9494
memcpy(mac, MAC_START, 3); // set first 3 bytes
9595
memcpy(mac + 3, localConfig.macEnd, 3); // set last 3 bytes
9696
#ifdef ENABLE_DHCP
97-
if (extraConfig.enableDhcp) {
97+
if (localConfig.enableDhcp) {
9898
dhcpSuccess = Ethernet.begin(mac);
9999
}
100-
if (!extraConfig.enableDhcp || dhcpSuccess == false) {
101-
Ethernet.begin(mac, localConfig.ip, extraConfig.dns, localConfig.gateway, localConfig.subnet);
100+
if (!localConfig.enableDhcp || dhcpSuccess == false) {
101+
Ethernet.begin(mac, localConfig.ip, localConfig.dns, localConfig.gateway, localConfig.subnet);
102102
}
103103
#else /* ENABLE_DHCP */
104104
Ethernet.begin(mac, localConfig.ip, {}, localConfig.gateway, localConfig.subnet); // No DNS
@@ -119,7 +119,7 @@ void (*resetFunc)(void) = 0; //declare reset function at address 0
119119

120120
#ifdef ENABLE_DHCP
121121
void maintainDhcp() {
122-
if (extraConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
122+
if (localConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
123123
uint8_t maintainResult = Ethernet.maintain();
124124
if (maintainResult == 1 || maintainResult == 3) { // renew failed or rebind failed
125125
dhcpSuccess = false;
@@ -148,7 +148,7 @@ void maintainUptime() {
148148
bool rollover() {
149149
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
150150
const unsigned long ROLLOVER = 0xFFFFFF00;
151-
for (byte i = 0; i < ERROR_LAST; i++) { // there is no counter for SLAVE_ERROR_0B_QUEUE
151+
for (byte i = 0; i < ERROR_LAST; i++) {
152152
if (errorCount[i] > ROLLOVER) {
153153
return true;
154154
}
@@ -157,23 +157,23 @@ bool rollover() {
157157
if (seconds > ROLLOVER) {
158158
return true;
159159
}
160-
if (serialTxCount > ROLLOVER || serialRxCount > ROLLOVER || ethTxCount > ROLLOVER || ethRxCount > ROLLOVER) {
161-
return true;
160+
for (byte i = 0; i < DATA_LAST; i++) {
161+
if (rtuCount[i] > ROLLOVER || ethCount[i] > ROLLOVER) {
162+
return true;
163+
}
162164
}
163165
#endif /* ENABLE_EXTRA_DIAG */
164166
return false;
165167
}
166168

167169
void resetStats() {
168170
memset(errorCount, 0, sizeof(errorCount));
169-
statsEepromTimer.sleep(0);
170171
#ifdef ENABLE_EXTRA_DIAG
171172
remaining_seconds = -(millis() / 1000);
172-
ethRxCount = 0;
173-
ethTxCount = 0;
174-
serialRxCount = 0;
175-
serialTxCount = 0;
173+
memset(rtuCount, 0, sizeof(rtuCount));
174+
memset(ethCount, 0, sizeof(ethCount));
176175
#endif /* ENABLE_EXTRA_DIAG */
176+
updateEeprom();
177177
}
178178

179179
void generateMac() {
@@ -187,7 +187,25 @@ void generateMac() {
187187
}
188188
}
189189

190-
190+
void updateEeprom() {
191+
eepromTimer.sleep(EEPROM_INTERVAL * 60UL * 60UL * 1000UL); // EEPROM_INTERVAL is in hours, sleep is in milliseconds!
192+
eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
193+
int address = CONFIG_START;
194+
EEPROM.put(address, eepromWrites);
195+
address += sizeof(eepromWrites);
196+
EEPROM.put(address, VERSION[0]);
197+
address += 1;
198+
EEPROM.put(address, localConfig);
199+
address += sizeof(localConfig);
200+
EEPROM.put(address, errorCount);
201+
address += sizeof(errorCount);
202+
#ifdef ENABLE_EXTRA_DIAG
203+
EEPROM.put(address, rtuCount);
204+
address += sizeof(rtuCount);
205+
EEPROM.put(address, ethCount);
206+
address += sizeof(ethCount);
207+
#endif /* ENABLE_EXTRA_DIAG */
208+
}
191209

192210
#if MAX_SOCK_NUM == 8
193211
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void recvUdp() {
4343
unsigned int msgLength = Udp.parsePacket();
4444
if (msgLength) {
4545
#ifdef ENABLE_EXTRA_DIAG
46-
ethRxCount += msgLength;
46+
ethCount[DATA_RX] += msgLength;
4747
#endif /* ENABLE_EXTRA_DIAG */
4848
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
4949
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address)..... no CRC
@@ -72,8 +72,8 @@ void recvUdp() {
7272
}
7373
Udp.endPacket();
7474
#ifdef ENABLE_EXTRA_DIAG
75-
ethTxCount += 5;
76-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
75+
ethCount[DATA_TX] += 5;
76+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
7777
#endif /* ENABLE_EXTRA_DIAG */
7878
}
7979
}
@@ -82,7 +82,7 @@ void recvUdp() {
8282
void recvTcp(EthernetClient &client) {
8383
unsigned int msgLength = client.available();
8484
#ifdef ENABLE_EXTRA_DIAG
85-
ethRxCount += msgLength;
85+
ethCount[DATA_RX] += msgLength;
8686
#endif /* ENABLE_EXTRA_DIAG */
8787
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
8888
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address).....
@@ -113,8 +113,8 @@ void recvTcp(EthernetClient &client) {
113113
}
114114
client.write(outBuffer, i);
115115
#ifdef ENABLE_EXTRA_DIAG
116-
ethTxCount += 5;
117-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
116+
ethCount[DATA_TX] += 5;
117+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
118118
#endif /* ENABLE_EXTRA_DIAG */
119119
}
120120
}
@@ -215,13 +215,13 @@ void deleteRequest() // delete request from queue
215215
}
216216

217217
void clearQueue() {
218-
queueHeaders.clear(); // <- eats memory!
218+
queueHeaders.clear();
219219
queueData.clear();
220220
scanReqInQueue = false;
221221
priorityReqInQueue = false;
222222
memset(socketInQueue, 0, sizeof(socketInQueue));
223223
memset(slaveStatus[SLAVE_ERROR_0B_QUEUE], 0, sizeof(slaveStatus[SLAVE_ERROR_0B_QUEUE]));
224-
sendTimer.sleep(0);
224+
sendMicroTimer.sleep(0);
225225
}
226226

227227
bool getSlaveStatus(const uint8_t slave, const byte status) {

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

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
void sendSerial() {
21-
if (!sendTimer.isOver()) {
21+
if (!sendMicroTimer.isOver()) {
2222
return;
2323
}
2424
if (queueHeaders.isEmpty()) {
@@ -62,7 +62,7 @@ void sendSerial() {
6262
txNdx = 0;
6363
mySerial.flush();
6464
#ifdef RS485_CONTROL_PIN
65-
// sendTimer.sleep(frameDelay()); // Short delay before we toggle the RS485_CONTROL_PIN and disable RS485 transmit. Not needed if we use flush()
65+
// sendMicroTimer.sleep(frameDelay()); // Short delay before we toggle the RS485_CONTROL_PIN and disable RS485 transmit. Not needed if we use flush()
6666
#endif /* RS485_CONTROL_PIN */
6767
serialState++;
6868
}
@@ -71,8 +71,8 @@ void sendSerial() {
7171
case 2: // DELAY:
7272
{
7373
#ifdef ENABLE_EXTRA_DIAG
74-
serialTxCount += myHeader.msgLen;
75-
serialTxCount += 2;
74+
rtuCount[DATA_TX] += myHeader.msgLen;
75+
rtuCount[DATA_TX] += 2;
7676
#endif
7777
#ifdef RS485_CONTROL_PIN
7878
digitalWrite(RS485_CONTROL_PIN, RS485_RECEIVE); // Disable RS485 Transmit
@@ -82,7 +82,7 @@ void sendSerial() {
8282
queueHeaders.unshift(myHeader);
8383
unsigned long delay = localConfig.serialTimeout;
8484
if (myHeader.requestType & SCAN_REQUEST) delay = SCAN_TIMEOUT; // fixed timeout for scan requests
85-
sendTimer.sleep(delay * 1000UL);
85+
sendMicroTimer.sleep(delay * 1000UL);
8686
serialState++;
8787
}
8888
break;
@@ -131,10 +131,10 @@ void recvSerial() {
131131
serialIn[rxNdx] = b;
132132
rxNdx++;
133133
} // if frame longer than maximum allowed, CRC will fail and errorCount[ERROR_RTU] will be recorded down the road
134-
recvTimer.sleep(charTimeOut());
135-
sendTimer.sleep(localConfig.frameDelay * 1000UL); // delay next serial write
134+
recvMicroTimer.sleep(charTimeOut());
135+
sendMicroTimer.sleep(localConfig.frameDelay * 1000UL); // delay next serial write
136136
}
137-
if (recvTimer.isOver() && rxNdx != 0) {
137+
if (recvMicroTimer.isOver() && rxNdx != 0) {
138138
// Process Serial data
139139
// Checks: 1) CRC; 2) address of incoming packet against first request in queue; 3) only expected responses are forwarded to TCP/UDP
140140
header myHeader = queueHeaders.first();
@@ -158,7 +158,7 @@ void recvSerial() {
158158
errorCount[ERROR_RTU]++;
159159
}
160160
#ifdef ENABLE_EXTRA_DIAG
161-
serialRxCount += rxNdx;
161+
rtuCount[DATA_RX] += rxNdx;
162162
#endif /* ENABLE_EXTRA_DIAG */
163163
rxNdx = 0;
164164
}
@@ -182,25 +182,24 @@ void sendResponse(const byte MBAP[], const byte PDU[], const int pduLength) {
182182
}
183183
Udp.endPacket();
184184
#ifdef ENABLE_EXTRA_DIAG
185-
ethTxCount += pduLength;
186-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
185+
ethCount[DATA_TX] += pduLength;
186+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
187187
#endif /* ENABLE_EXTRA_DIAG */
188188
} else if (myHeader.requestType & TCP_REQUEST) {
189189
byte sock = myHeader.requestType & TCP_REQUEST_MASK;
190190
EthernetClient client = EthernetClient(sock);
191-
// if (W5100.readSnSR(sock) == SnSR::ESTABLISHED && W5100.readSnDPORT(sock) == myHeader.remPort) { // Check remote port should be enough or check also rem IP?
192-
if (W5100.readSnSR(sock) == SnSR::ESTABLISHED) { // Check remote port should be enough or check also rem IP?
191+
if (W5100.readSnSR(sock) == SnSR::ESTABLISHED && W5100.readSnDPORT(sock) == myHeader.remPort) { // Check remote port should be enough or check also rem IP?
193192
if (localConfig.enableRtuOverTcp) client.write(PDU, pduLength);
194193
else {
195194
client.write(MBAP, 6);
196195
client.write(PDU, pduLength - 2); //send without CRC
197196
}
198197
#ifdef ENABLE_EXTRA_DIAG
199-
ethTxCount += pduLength;
200-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
198+
ethCount[DATA_TX] += pduLength;
199+
if (!localConfig.enableRtuOverTcp) ethCount[DATA_TX] += 4;
201200
#endif /* ENABLE_EXTRA_DIAG */
202201
} // TODO TCP Connection Error
203-
} // else SCAN_REQUEST (no ethTxCount, but yes delete request)
202+
} // else SCAN_REQUEST (no ethCount[DATA_TX], but yes delete request)
204203
deleteRequest();
205204
}
206205

0 commit comments

Comments
 (0)