├── .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 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |   57 | 58 | 62 |
63 | 64 |   68 | 69 | 75 |
76 | 77 |   83 | 84 | 88 |
89 | 90 |   95 | 96 | 102 |
103 | 104 |   109 | 110 | 116 |
117 | 118 |   124 | 125 | 130 |
131 | 132 |   138 | 139 | 145 |
146 | 147 |   152 | 153 | 159 |
                               160 | 161 | 166 |
                               167 | 168 | 173 |
                               174 | 175 | 180 | 181 |
                               182 | 183 | 188 |

189 |
43 |
190 | 191 |
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 | ![Screenshot](/build/flashing.PNG) 47 | ![Screenshot](/screenshot.jpeg) 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