├── .gitattributes
├── HTML.h
├── Implementation to PLC
├── TIA Portal example
│ └── TIA Portal Modbus TCP Example with ESP32.zap16
└── Twincat 3 example + library
│ ├── ESP32 ModbusTCP example with its own library.tnzip
│ └── Esp32ModbusTCP.library
├── LICENSE
├── README.md
├── WiFiManager.h
├── build
├── flash_download_tool_3.9.5.exe
├── flashing.PNG
└── generated firmware.bin
├── esp32-modbus-wifi-manager.ino
└── screenshot.jpeg
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/HTML.h:
--------------------------------------------------------------------------------
1 | const char INDEX_HTML[] PROGMEM = R"=====(
2 |
3 |
4 |
5 |
6 | iDispaly Navigator
7 |
8 |
29 |
30 |
31 |
32 |
33 |
Board configuration
34 |
192 |
193 |
194 |
195 |
196 | )=====";
--------------------------------------------------------------------------------
/Implementation to PLC/TIA Portal example/TIA Portal Modbus TCP Example with ESP32.zap16:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/Implementation to PLC/TIA Portal example/TIA Portal Modbus TCP Example with ESP32.zap16
--------------------------------------------------------------------------------
/Implementation to PLC/Twincat 3 example + library/ESP32 ModbusTCP example with its own library.tnzip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/Implementation to PLC/Twincat 3 example + library/ESP32 ModbusTCP example with its own library.tnzip
--------------------------------------------------------------------------------
/Implementation to PLC/Twincat 3 example + library/Esp32ModbusTCP.library:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/Implementation to PLC/Twincat 3 example + library/Esp32ModbusTCP.library
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Evis Vasiu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firmware that makes ESP32 function as a remote I/O device with Modbus-TCP + configuration interface
2 |
3 | ### Based on the following libraries
4 | Modbus: https://github.com/emelianov/modbus-esp8266
5 | Wifi manager: https://github.com/tzapu/WiFiManager
6 | Programmed with Arduino IDE.
7 | Tested on TIA Portal and Twincat3.
8 |
9 | ### Usage:
10 | * Put pin 15 as HIGH for 3 seconds to start Wifi AP mode.
11 | * Connect to Access Point with SSID: "EvisLAB-ESP32", Key: 12345678
12 | * Go to 192.168.4.1 on your preferred browser
13 | * Select pinMode options by the dropdown list for each pin. "Unused" is the default option
14 | * Write your preferred static IP and the Gateway.
15 | * Board will restart instantly after saving and be ready to connect to any Modbus-TCP master(client).
16 |
17 | ## Notes:
18 | ### Modbus functions and addresses:
19 | * Write coils FC-15: 0 - 39 (Siemens: 1 - 40)
20 | - connected to Digital Outputs
21 | * Read inputs FC-02: 100 - 139 (Siemens: 10101 - 10140)
22 | - connected to Digital Inputs
23 | * Write Holding Registers FC-16: 200 - 239 (Siemens: 40201 - 40240)
24 | - connected to Analog Output (DAC), PWM pins
25 | * Read Input Registers FC-04: 300 - 339 (Siemens: 30301 - 30340)
26 | - connected to Analog Inputs
27 | * Port: 502
28 | * Device ID: 1
29 |
30 | ### Signal parameters
31 | * Analog Inputs:
32 | - Resolution 12 bit
33 | * Analog Outputs (DCA):
34 | - Resolution 8 bit
35 | * PWM:
36 | - Resolution 12 bit
37 | - Frequency 10kHz
38 |
39 | ## Flashing methods
40 | * Arduino IDE
41 | * ESP flash tool
42 | - Load the binary file and set the offset as 0x10000 (default for ESP32)
43 | - While keeping the "boot" button pressed, plug the USB cable, choose the COM port and then press Start.
44 | - After flashing start, release the "boot" button.
45 |
46 | 
47 | 
48 |
--------------------------------------------------------------------------------
/WiFiManager.h:
--------------------------------------------------------------------------------
1 |
2 | IPAddress local_IP;
3 | IPAddress gateway;
4 | IPAddress subnet(255, 255, 255, 0);
5 |
6 | void handleNotFound() {
7 | String message = "File Not Found\n\n";
8 | message += "URI: ";
9 | message += server.uri();
10 | message += "\nMethod: ";
11 | message += (server.method() == HTTP_GET) ? "GET" : "POST";
12 | message += "\nArguments: ";
13 | message += server.args();
14 | message += "\n";
15 | for (uint8_t i = 0; i < server.args(); i++) {
16 | message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
17 | }
18 | server.send(404, "text/plain", message);
19 | }
20 |
21 |
22 | //Returns: true if save successful, false if unsuccessful
23 | bool writeToMemory(String ssid, String pass, String ipstring, String gatewaystring, String pin0, String pin1, String pin2, String pin3, String pin4, String pin5, String pin6,
24 | String pin7, String pin8, String pin9, String pin10, String pin11, String pin12, String pin13, String pin14, String pin15, String pin16, String pin17, String pin18,
25 | String pin19, String pin20, String pin21, String pin22, String pin23, String pin24, String pin25, String pin26, String pin27, String pin28, String pin29, String pin30,
26 | String pin31, String pin32, String pin33, String pin34, String pin35, String pin36, String pin37, String pin38, String pin39)
27 | {
28 | char buff1[29];
29 | char buff2[29];
30 | char buff3[19];
31 | char buff4[19];
32 |
33 | ssid.toCharArray(buff1,30);
34 | pass.toCharArray(buff2,30);
35 | ipstring.toCharArray(buff3,20);
36 | gatewaystring.toCharArray(buff4,20);
37 |
38 | EEPROM.writeString(100,buff1);
39 | EEPROM.writeString(130,buff2);
40 | EEPROM.writeString(160,buff3);
41 | EEPROM.writeString(180,buff4);
42 |
43 | EEPROM.write(50,pin0.toInt());
44 | EEPROM.write(51,pin1.toInt());
45 | EEPROM.write(52,pin2.toInt());
46 | EEPROM.write(53,pin3.toInt());
47 | EEPROM.write(54,pin4.toInt());
48 | EEPROM.write(55,pin5.toInt());
49 | EEPROM.write(56,pin6.toInt());
50 | EEPROM.write(57,pin7.toInt());
51 | EEPROM.write(58,pin8.toInt());
52 | EEPROM.write(59,pin9.toInt());
53 | EEPROM.write(60,pin10.toInt());
54 | EEPROM.write(61,pin11.toInt());
55 | EEPROM.write(62,pin12.toInt());
56 | EEPROM.write(63,pin13.toInt());
57 | EEPROM.write(64,pin14.toInt());
58 | EEPROM.write(65,pin15.toInt());
59 | EEPROM.write(66,pin16.toInt());
60 | EEPROM.write(67,pin17.toInt());
61 | EEPROM.write(68,pin18.toInt());
62 | EEPROM.write(69,pin19.toInt());
63 | EEPROM.write(70,pin20.toInt());
64 | EEPROM.write(71,pin21.toInt());
65 | EEPROM.write(72,pin22.toInt());
66 | EEPROM.write(73,pin23.toInt());
67 | EEPROM.write(74,pin24.toInt());
68 | EEPROM.write(75,pin25.toInt());
69 | EEPROM.write(76,pin26.toInt());
70 | EEPROM.write(77,pin27.toInt());
71 | EEPROM.write(78,pin28.toInt());
72 | EEPROM.write(79,pin29.toInt());
73 | EEPROM.write(80,pin30.toInt());
74 | EEPROM.write(81,pin31.toInt());
75 | EEPROM.write(82,pin32.toInt());
76 | EEPROM.write(83,pin33.toInt());
77 | EEPROM.write(84,pin34.toInt());
78 | EEPROM.write(85,pin35.toInt());
79 | EEPROM.write(86,pin36.toInt());
80 | EEPROM.write(87,pin37.toInt());
81 | EEPROM.write(88,pin38.toInt());
82 | EEPROM.write(89,pin39.toInt());
83 |
84 | delay(100);
85 |
86 |
87 | String s = EEPROM.readString(100);
88 | String p = EEPROM.readString(130);
89 | //#if DEBUG
90 | Serial.print("Stored SSID, password, are: ");
91 | Serial.print(s);
92 | Serial.print(" / ");
93 | Serial.print(p);
94 | //#endif
95 | if(ssid == s && pass == p){
96 | return true;
97 | }else{
98 | return false;
99 | }
100 | }
101 |
102 | /*
103 | * Function for handling form
104 | */
105 | void handleSubmit(){
106 | String response_success="Success
";
107 | response_success +="Device will restart in 3 seconds
";
108 | String response_error="Error
";
109 | response_error +="Go backto try again";
110 | //"5" = null (not used)
111 | if(writeToMemory(String(server.arg("ssid")),String(server.arg("password")),String(server.arg("ip")),String(server.arg("gateway")), String(server.arg("pin0")),"5",
112 | String(server.arg("pin2")), "5", String(server.arg("pin4")), "5", "5", "5", "5", "5", "5","5", String(server.arg("pin12")), String(server.arg("pin13")),
113 | String(server.arg("pin14")), "5", String(server.arg("pin16")), String(server.arg("pin17")), String(server.arg("pin18")), String(server.arg("pin19")), "5", "5", "5",
114 | String(server.arg("pin23")), "5", String(server.arg("pin25")), String(server.arg("pin26")), String(server.arg("pin27")), "5", "5", "5", "5", String(server.arg("pin32")),
115 | String(server.arg("pin33")), String(server.arg("pin34")), String(server.arg("pin35")), String(server.arg("pin36")), "5", "5", String(server.arg("pin39"))))
116 |
117 | {
118 | server.send(200, "text/html", response_success);
119 | EEPROM.commit();
120 | delay(3000);
121 | ESP.restart();
122 | }else{
123 | server.send(200, "text/html", response_error);
124 | }
125 | }
126 |
127 | /*
128 | * Function for home page
129 | */
130 | void handleRoot() {
131 | if (server.hasArg("ssid")&& server.hasArg("password")) {
132 | handleSubmit();
133 | }
134 | else {
135 | server.send(200, "text/html", INDEX_HTML);
136 | }
137 | }
138 |
139 | /*
140 | * Function for loading form
141 | * Returns: false if no WiFi creds in EEPROM
142 | */
143 | bool loadWIFICredsForm(){
144 | String s = EEPROM.readString(100);
145 | String p = EEPROM.readString(130);
146 |
147 | const char* APname = "EvisLAB-ESP32";
148 | const char* password = "12345678";
149 | Serial.println("Setting Access Point...");
150 | WiFi.softAP(APname, password);
151 | IPAddress IP = WiFi.softAPIP();
152 | Serial.print("AP IP address: ");
153 | Serial.println(IP);
154 | server.on("/", handleRoot);
155 | server.onNotFound(handleNotFound);
156 | server.begin();
157 | Serial.println("HTTP server started");
158 | while(s.length() <= 0 && p.length() <= 0){
159 | server.handleClient();
160 | delay(100);
161 | }
162 |
163 | return false;
164 | }
165 |
166 |
167 |
168 | //Returns: true if not empty, false if empty
169 |
170 | bool CheckWIFICreds(){
171 | Serial.println("Checking WIFI credentials");
172 | String s = EEPROM.readString(100);
173 | String p = EEPROM.readString(130);
174 |
175 | if(s.length() < 1 && p.length() < 1){
176 | return false;
177 | }
178 | String ipstring = EEPROM.readString(160);
179 | String gatewaystring = EEPROM.readString(180);
180 |
181 |
182 |
183 | //#if DEBUG
184 | Serial.print("Found credentials: ");
185 | Serial.print(s);
186 | Serial.print("/");
187 | Serial.print(p);
188 | delay(10);
189 | local_IP.fromString(ipstring);
190 | gateway.fromString(gatewaystring);
191 |
192 | WiFi.config(local_IP, gateway, subnet);
193 | // We start by connecting to a WiFi network
194 | Serial.println();
195 | Serial.print("Connecting to ");
196 | Serial.println(s);
197 |
198 | WiFi.begin(s.c_str(), p.c_str());
199 | int i = 0;
200 | while ((WiFi.status() != WL_CONNECTED) && (i < 20 )) {
201 | delay(500);
202 | i++;
203 | Serial.print(".");
204 | }
205 |
206 | Serial.println("");
207 | Serial.println("WiFi connected");
208 | Serial.println("IP address: ");
209 | Serial.println(WiFi.localIP());
210 | return true;
211 |
212 | }
213 |
214 | void wipeEEPROM(){
215 | for(int i=0;i<400;i++){
216 | EEPROM.writeByte(i,0);
217 | }
218 | EEPROM.commit();
219 | }
220 |
221 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/build/flash_download_tool_3.9.5.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/build/flash_download_tool_3.9.5.exe
--------------------------------------------------------------------------------
/build/flashing.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/build/flashing.PNG
--------------------------------------------------------------------------------
/build/generated firmware.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evisvasiu/esp32-modbus-wifi-manager/386a86b55507210ddc7dab04a667d3a3d64bc9d1/build/generated firmware.bin
--------------------------------------------------------------------------------
/esp32-modbus-wifi-manager.ino:
--------------------------------------------------------------------------------
1 | #include "EEPROM.h"
2 | #include "HTML.h"
3 | #include
4 | #include
5 |
6 | WebServer server(80);
7 | #include "WiFiManager.h"
8 |
9 |
10 | //ModbusIP object
11 | ModbusIP mb;
12 | long ts;
13 |
14 | int pinvalues[40]; //Storing all current pin values to this array
15 | int pinconfig[40]; //pin configuration
16 | int pinctrlvalues[40]; //Holding register values
17 | int pwmchannels[20]; //Designated PWM channel addresses
18 |
19 |
20 | void setup() {
21 |
22 | //Assigning PWM channels to the 4 designated PWM pins.
23 | pwmchannels[4] = 0;
24 | pwmchannels[13] = 2;
25 | pwmchannels[18] = 4;
26 | pwmchannels[19] = 6;
27 |
28 | Serial.begin(115200);
29 | EEPROM.begin(400);
30 | pinMode(15, INPUT); //erasing EEPROM
31 |
32 |
33 | if(!CheckWIFICreds()){
34 | Serial.println("No WIFI credentials stored in memory. Loading HTML form...");
35 | while(loadWIFICredsForm());
36 | }
37 |
38 |
39 | //reading and printing pin configuration stored in EEPROM
40 | Serial.println("Pin configuration:");
41 | for (int i=0;i<40;i++){
42 | pinconfig[i] = EEPROM.read(i+50);
43 | Serial.print(EEPROM.read(i+50));
44 | Serial.print(" ");
45 | }
46 |
47 |
48 | mb.server(); //Start Modbus IP
49 |
50 |
51 | for (int i=0;i<40;i++){
52 |
53 | if (pinconfig[i] == 0){
54 | //digital input
55 | pinMode(i, INPUT);
56 | }
57 |
58 | else if (pinconfig[i] == 1){
59 | //digital output
60 | pinMode(i, OUTPUT);
61 | }
62 |
63 | else if (pinconfig[i] == 4){
64 | //PWM, 10kHz, 12bit. only high speed channel
65 | ledcSetup(pwmchannels[i], 10000, 12);
66 | ledcAttachPin(i, pwmchannels[i]);
67 | }
68 |
69 | /*
70 | Register offsets:
71 | Write Coils: 0
72 | Discrete Inputs : 100
73 | Holding registers : 200
74 | Input registers : 300
75 | */
76 |
77 | mb.addCoil(i);
78 | mb.addIsts(100+i);
79 | mb.addHreg(200+i);
80 | mb.addIreg(300+i);
81 |
82 | //asigning temporary values to output pins for debugging purposes
83 | if (pinconfig[i] < 5 ){
84 | //33333 is the initial value. (Assigned for debugging)
85 | pinvalues[i] = 33333;
86 | }
87 |
88 | else {
89 | //22222 is value assigned to unused pins
90 | pinvalues[i] = 22222;
91 | pinctrlvalues[i] = 22222;
92 | }
93 | }
94 |
95 | ts = millis();
96 | }
97 |
98 | void loop() {
99 |
100 | mb.task(); //keeping modbus active
101 |
102 | //modbus refresh rate
103 | if (millis() > ts + 100) {
104 | ts = millis();
105 |
106 | for (int i = 0; i<40; i++){
107 |
108 | if (pinconfig[i] == 0){
109 |
110 | //this pin is digital input
111 | bool binput = (bool)digitalRead(i);
112 | pinvalues[i] = (int)binput;
113 | mb.Ists(100+i, binput);
114 | }
115 |
116 | else if (pinconfig[i] == 1){
117 | //this pin is digital output
118 | bool boutput = (int)mb.Coil(i);
119 | pinctrlvalues[i] = (int)boutput;
120 | pinvalues[i] = (int)boutput;
121 | digitalWrite(i, boutput);
122 | }
123 |
124 | else if (pinconfig[i] == 2) {
125 | //this pin is analog input
126 | int temp_value = averagingRawValues(i);
127 | pinvalues[i] = temp_value;
128 | mb.Ireg(300+i, temp_value);
129 | }
130 |
131 | else if (pinconfig[i] == 3) {
132 | //this pin is analog output 8bit
133 | int temp_value = (int)mb.Hreg(200+i);
134 | pinctrlvalues[i] = temp_value;
135 | pinvalues[i] = temp_value;
136 | dacWrite(i, temp_value);
137 | }
138 |
139 | else if (pinconfig[i] == 4) {
140 | //this pin is PWM.
141 | int temp_value = (int)mb.Hreg(200+i);
142 | pinctrlvalues[i] = temp_value;
143 | pinvalues[i] = temp_value;
144 | ledcWrite(pwmchannels[i], temp_value); //12bit
145 | }
146 | }
147 | /*
148 | Serial.println("Input registers:");
149 | for (int i = 0; i<40; i++){
150 | Serial.print(pinvalues[i]);
151 | Serial.print(" ");
152 | }
153 | Serial.println();
154 |
155 | Serial.println("Hold registers:");
156 | for (int i = 0; i<40; i++){
157 | Serial.print(pinctrlvalues[i]);
158 | Serial.print(" ");
159 | }
160 | Serial.println();*/
161 | }
162 |
163 | long reset_delay = millis();
164 | while (digitalRead(15) == HIGH){
165 | //3 Seconds delay to reset the board.
166 | if (millis() > reset_delay + 3000){
167 | Serial.println("Wiping WiFi credentials from memory...");
168 | wipeEEPROM();
169 | while(loadWIFICredsForm());
170 | }
171 | }
172 | }
173 |
174 | int averagingRawValues(int pin) {
175 | int temp_sum = 0;
176 | int max_reads = 5;
177 | for (int i=0; i