Skip to content

Commit da71369

Browse files
committed
Simplify EEPROM read / write
1 parent d584400 commit da71369

14 files changed

+270
-263
lines changed

README.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Arduino-based Modbus RTU to Modbus TCP/UDP gateway with web interface. Allows yo
1111
- [IP Settings](#ip-settings)
1212
- [TCP/UDP Settings](#tcpudp-settings)
1313
- [RTU Settings](#rtu-settings)
14+
- [Tools](#tools)
1415
* [Integration](#integration)
1516
- [Loxone](#loxone)
1617
- [Home Assistant](#home-assistant)
@@ -73,22 +74,25 @@ Allows you to connect your Modbus devices (such as sensors, energy meters, HVAC
7374
# Hardware
7475
Get the hardware (cheap clones from China are sufficient) and connect together:
7576

76-
* **Arduino Nano, Uno or Mega** (and possibly other boards with Atmel chips).<br>On Mega you have to configure Serial in advanced settings in the sketch.
77+
* **Arduino Nano, Uno or Mega** (and possibly other boards with ATmega chips).<br>On Mega you have to configure Serial in advanced settings in the sketch.
7778
* **Ethernet shield with WIZnet chip (W5100, W5200 or W5500)**.<br>The ubiquitous W5100 shield for Uno/Mega is sufficient. If available, I recommend W5500 Ethernet Shield. You can also use combo board MCU + ethernet (such as ATmega328 + W5500 board from Keyestudio).<br>ATTENTION: Ethernet shields with ENC28J60 chip will not work !!!
78-
* **TTL to RS485 module with an automatic flow direction control**.<br>You can buy cheap modules with MAX3485, MAX13487, SP485 or SP3485 chips (some of these modules are branded as "XY-017", "XY-485", "XY-G485", etc.) from Aliexpress and other marketplaces. Use stabilized external power supply 5V for both Arduino (incl. ethernet shield) and the RS485 module.<br>ATTENTION: Modules with MAX485 chip will work (use pin 6 for DE,RE), but are NOT recommended (no auto-direction, no ESD protection, no hot-swap protection)!!!
79+
* **TTL to RS485 module with an automatic flow direction control**.<br>You can buy cheap modules with MAX3485, MAX13487, SP485 or SP3485 chips (some of these modules are branded as "XY-017", "XY-485", "XY-G485", etc.) from Aliexpress and other marketplaces.<br>ATTENTION: Modules with MAX485 chip will work (use pin 6 for DE+RE), but are NOT recommended (no auto-direction, no ESD protection, no hot-swap protection) !!!
80+
* **External power supply**.<br>Use regulated 5V external power supply for both the Arduino (+ the ethernet shield) and the RS485 module.<br>ATTENTION: By using the 5V pin, you are bypassing Arduino's built-in voltage regulator and reverse-polarity protection curcuit. Make sure your external power supply does not exceed 5,5V !!!
7981

8082
<img src="pics/fritzing.png" alt="fritzing" />
8183

8284

8385
Here is my HW setup:
84-
Terminal shield + Arduino Nano + W5500 ethernet shield (RobotDyn, no longer available) + XY-017 TTL to RS485 module (automatic flow control)
86+
Terminal shield + Arduino Nano + W5500 ethernet shield (from RobotDyn, no longer available) + TTL to RS485 module (automatic flow control)
8587
<img src="pics/HW.jpg" alt="HW" style="zoom:100%;" />
8688

8789
# Firmware
8890

8991
You can either:
90-
- ** Download and flash my pre-compiled firmware** from "Releases".
91-
- **Compile your own firmware**. Download this repository (all *.ino files) and open arduino-modbus-rtu-tcp-gateway.ino in Arduino IDE. Download all required libraries (they are available in "library manager"). If you want, you can check the default factory settings (can be later changed via web interface) and advanced settings (can only be changed in the sketch). Compile and upload your program to Arduino.
92+
- **Download and flash my pre-compiled firmware** from "Releases".
93+
- **Compile your own firmware**. Download this repository (all *.ino files) and open arduino-modbus-rtu-tcp-gateway.ino in Arduino IDE. If you want, you can check advanced_settings.h for advanced settings (can only be changed in the sketch) and for default factory settings (can be later changed via web interface). Download all required libraries, compile and upload your program to Arduino. The program uses the following external libraries (all are available in Arduino IDE's "library manager"):
94+
- CircularBuffer (https://github.com/rlogiacco/CircularBuffer)
95+
- StreamLib (https://github.com/jandrassy/StreamLib)
9296

9397
Connect your Arduino to ethernet and use your web browser to access the web interface on default IP: http://192.168.1.254
9498
Enjoy :-)
@@ -99,14 +103,12 @@ Enjoy :-)
99103
## System Info
100104
<img src="pics/modbus1.png" alt="modbus1" style="zoom:100%;" />
101105

102-
**Load Default Settings**. Loads default settings (see DEFAULT_CONFIG in advanced settings). MAC address is retained.
103-
104-
**Reboot**.
105-
106106
**EEPROM Health**. Keeps track of EEPROM write cycles (this counter is persistent, never cleared during factory resets). Replace your Arduino once you reach 100 000 write cycles (with 6 hours EEPROM_INTERVAL you have more than 50 years lifespan).
107107

108108
**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.
109109

110+
**Ethernet Chip**. Wiznet chip on the ethernet shield.
111+
110112
**MAC Address**. First 3 bytes are fixed 90:A2:DA, remaining 3 bytes are random. You can also set manual MAC in IP Settings.
111113

112114
## Modbus Status
@@ -197,6 +199,13 @@ Enjoy :-)
197199

198200
**Attempts**. Number of attempts before error (Code 11) is sent back to the Modbus TCP/UDP master.
199201

202+
## Tools
203+
<img src="pics/modbus6.png" alt="modbus6" style="zoom:100%;" />
204+
205+
**Load Default Settings**. Loads default settings (see DEFAULT_CONFIG in advanced settings). MAC address is retained.
206+
207+
**Reboot**.
208+
200209
# Integration
201210

202211
This gateway adheres to the Modbus protocol specifications, so you can use it to connect any compliant Modbus RTU slave (Modbus device) with any compliant Modbus TCP/UDP master (such as home automation system). Here is a quick overview how you can integrate the gateway into the most popular home automation systems:

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

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848

4949
void startSerial() {
50-
mySerial.begin((localConfig.baud * 100UL), localConfig.serialConfig);
50+
mySerial.begin((data.config.baud * 100UL), data.config.serialConfig);
5151
#ifdef RS485_CONTROL_PIN
5252
pinMode(RS485_CONTROL_PIN, OUTPUT);
5353
digitalWrite(RS485_CONTROL_PIN, RS485_RECEIVE); // Init Transceiver
@@ -58,25 +58,25 @@ void startSerial() {
5858
byte bitsPerChar() {
5959
byte bits =
6060
1 + // start bit
61-
(((localConfig.serialConfig & 0x06) >> 1) + 5) + // data bits
62-
(((localConfig.serialConfig & 0x08) >> 3) + 1); // stop bits
63-
if (((localConfig.serialConfig & 0x30) >> 4) > 1) bits += 1; // parity bit (if present)
61+
(((data.config.serialConfig & 0x06) >> 1) + 5) + // data bits
62+
(((data.config.serialConfig & 0x08) >> 3) + 1); // stop bits
63+
if (((data.config.serialConfig & 0x30) >> 4) > 1) bits += 1; // parity bit (if present)
6464
return bits;
6565
}
6666

6767
// character timeout in micros
6868
uint32_t charTimeOut() {
69-
if (localConfig.baud <= 192) {
70-
return (15000UL * bitsPerChar()) / localConfig.baud; // inter-character time-out should be 1,5T
69+
if (data.config.baud <= 192) {
70+
return (15000UL * bitsPerChar()) / data.config.baud; // inter-character time-out should be 1,5T
7171
} else {
7272
return 750;
7373
}
7474
}
7575

7676
// minimum frame delay in micros
7777
uint32_t frameDelay() {
78-
if (localConfig.baud <= 192) {
79-
return (35000UL * bitsPerChar()) / localConfig.baud; // inter-frame delay should be 3,5T
78+
if (data.config.baud <= 192) {
79+
return (35000UL * bitsPerChar()) / data.config.baud; // inter-frame delay should be 3,5T
8080
} else {
8181
return 1750; // 1750 μs
8282
}
@@ -91,20 +91,20 @@ void startEthernet() {
9191
delay(ETH_RESET_DELAY);
9292
}
9393
#ifdef ENABLE_DHCP
94-
if (localConfig.enableDhcp) {
95-
dhcpSuccess = Ethernet.begin(mac);
94+
if (data.config.enableDhcp) {
95+
dhcpSuccess = Ethernet.begin(data.mac);
9696
}
97-
if (!localConfig.enableDhcp || dhcpSuccess == false) {
98-
Ethernet.begin(mac, localConfig.ip, localConfig.dns, localConfig.gateway, localConfig.subnet);
97+
if (!data.config.enableDhcp || dhcpSuccess == false) {
98+
Ethernet.begin(data.mac, data.config.ip, data.config.dns, data.config.gateway, data.config.subnet);
9999
}
100100
#else /* ENABLE_DHCP */
101-
Ethernet.begin(mac, localConfig.ip, {}, localConfig.gateway, localConfig.subnet); // No DNS
101+
Ethernet.begin(data.mac, data.config.ip, {}, data.config.gateway, data.config.subnet); // No DNS
102102
#endif /* ENABLE_DHCP */
103103
W5100.setRetransmissionTime(TCP_RETRANSMISSION_TIMEOUT);
104104
W5100.setRetransmissionCount(TCP_RETRANSMISSION_COUNT);
105-
modbusServer = EthernetServer(localConfig.tcpPort);
106-
webServer = EthernetServer(localConfig.webPort);
107-
Udp.begin(localConfig.udpPort);
105+
modbusServer = EthernetServer(data.config.tcpPort);
106+
webServer = EthernetServer(data.config.webPort);
107+
Udp.begin(data.config.udpPort);
108108
modbusServer.begin();
109109
webServer.begin();
110110
#if MAX_SOCK_NUM > 4
@@ -116,7 +116,7 @@ void (*resetFunc)(void) = 0; //declare reset function at address 0
116116

117117
#ifdef ENABLE_DHCP
118118
void maintainDhcp() {
119-
if (localConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
119+
if (data.config.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
120120
byte maintainResult = Ethernet.maintain();
121121
if (maintainResult == 1 || maintainResult == 3) { // renew failed or rebind failed
122122
dhcpSuccess = false;
@@ -146,7 +146,7 @@ bool rollover() {
146146
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
147147
const uint32_t ROLLOVER = 0xFFFFFF00;
148148
for (byte i = 0; i < ERROR_LAST; i++) {
149-
if (errorCount[i] > ROLLOVER) {
149+
if (data.errorCnt[i] > ROLLOVER) {
150150
return true;
151151
}
152152
}
@@ -155,56 +155,41 @@ bool rollover() {
155155
return true;
156156
}
157157
for (byte i = 0; i < DATA_LAST; i++) {
158-
if (rtuCount[i] > ROLLOVER || ethCount[i] > ROLLOVER) {
158+
if (data.rtuCnt[i] > ROLLOVER || data.ethCnt[i] > ROLLOVER) {
159159
return true;
160160
}
161161
}
162162
#endif /* ENABLE_EXTRA_DIAG */
163163
return false;
164164
}
165165

166+
// resets counters to 0: data.errorCnt, data.rtuCnt, data.ethCnt
166167
void resetStats() {
167-
memset(errorCount, 0, sizeof(errorCount));
168+
memset(data.errorCnt, 0, sizeof(data.errorCnt));
168169
#ifdef ENABLE_EXTRA_DIAG
170+
memset(data.rtuCnt, 0, sizeof(data.rtuCnt));
171+
memset(data.ethCnt, 0, sizeof(data.ethCnt));
169172
remaining_seconds = -(millis() / 1000);
170-
memset(rtuCount, 0, sizeof(rtuCount));
171-
memset(ethCount, 0, sizeof(ethCount));
172173
#endif /* ENABLE_EXTRA_DIAG */
173-
updateEeprom();
174174
}
175175

176+
// generate new MAC (bytes 0, 1 and 2 are static, bytes 3, 4 and 5 are generated randomly)
176177
void generateMac() {
177178
// Marsaglia algorithm from https://github.com/RobTillaart/randomHelpers
178179
seed1 = 36969L * (seed1 & 65535L) + (seed1 >> 16);
179180
seed2 = 18000L * (seed2 & 65535L) + (seed2 >> 16);
180181
uint32_t randomBuffer = (seed1 << 16) + seed2; /* 32-bit random */
181-
memcpy(mac, MAC_START, 3); // set first 3 bytes
182+
memcpy(data.mac, MAC_START, 3); // set first 3 bytes
182183
for (byte i = 0; i < 3; i++) {
183-
mac[i + 3] = randomBuffer & 0xFF; // random last 3 bytes
184+
data.mac[i + 3] = randomBuffer & 0xFF; // random last 3 bytes
184185
randomBuffer >>= 8;
185186
}
186187
}
187188

188189
void updateEeprom() {
189190
eepromTimer.sleep(EEPROM_INTERVAL * 60UL * 60UL * 1000UL); // EEPROM_INTERVAL is in hours, sleep is in milliseconds!
190-
eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
191-
uint16_t address = CONFIG_START;
192-
EEPROM.put(address, eepromWrites);
193-
address += sizeof(eepromWrites);
194-
EEPROM.put(address, VERSION[0]);
195-
address += 1;
196-
EEPROM.put(address, mac);
197-
address += 6;
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 */
191+
data.eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
192+
EEPROM.put(DATA_START, data);
208193
}
209194

210195
#if MAX_SOCK_NUM == 8
@@ -243,7 +228,7 @@ void manageSockets() {
243228
case SnSR::SYNRECV:
244229
{
245230
lastSocketUse[s] = millis();
246-
if (W5100.readSnPORT(s) == localConfig.webPort) {
231+
if (W5100.readSnPORT(s) == data.config.webPort) {
247232
webListening = s;
248233
} else {
249234
modbusListening = s;
@@ -275,8 +260,8 @@ void manageSockets() {
275260
W5100.execCmdSn(s, Sock_DISCON); // send DISCON command...
276261
lastSocketUse[s] = millis(); // record time at which it was sent...
277262
// status becomes LAST_ACK for short time
278-
} else if (((W5100.readSnPORT(s) == localConfig.webPort && sockAge > WEB_IDLE_TIMEOUT)
279-
|| (W5100.readSnPORT(s) == localConfig.tcpPort && sockAge > (localConfig.tcpTimeout * 1000UL)))
263+
} else if (((W5100.readSnPORT(s) == data.config.webPort && sockAge > WEB_IDLE_TIMEOUT)
264+
|| (W5100.readSnPORT(s) == data.config.tcpPort && sockAge > (data.config.tcpTimeout * 1000UL)))
280265
&& sockAge > maxAge) {
281266
oldest = s; // record the socket number...
282267
maxAge = sockAge; // and make its age the new max age.
@@ -291,7 +276,7 @@ void manageSockets() {
291276

292277
if (dataAvailable != MAX_SOCK_NUM) {
293278
EthernetClient client = EthernetClient(dataAvailable);
294-
if (W5100.readSnPORT(dataAvailable) == localConfig.webPort) {
279+
if (W5100.readSnPORT(dataAvailable) == data.config.webPort) {
295280
recvWeb(client);
296281
} else {
297282
recvTcp(client);

0 commit comments

Comments
 (0)