Overview
The SIMATIC IOT2000 family — IOT2020 and IOT2040 — is an Intel Quark (x86 / i586 class) based industrial gateway that runs the Yocto Linux image supplied by Siemens. Siemens positions the platform as a flexible edge device that bridges shop-floor protocols (Modbus TCP, S7, PROFINET, OPC UA) with cloud services. Although the SDK reference is Eclipse with the Intel System Studio IoT Edition, a community-maintained Arduino core exists that exposes the IOT2000 hardware to the standard Arduino IDE 1.8.x workflow.
This tutorial covers the verified configuration of:
- Two independent Ethernet interfaces (X1 = eth0, X2 = eth1) for segregated field/cloud networks.
- Three serial channels: Serial3 (X30), Serial4 (X31), and the USB device port (X60).
- RS-485 / RS-422 mode selection and bus termination control.
- Sketch upload over LAN (TFTP-based deployment).
- Known UDP
remoteIP()/remotePort()regression and the documented workaround.
Read the SIMATIC IOT2040 operating instructions and the SIMATIC IOT2000 SDK user guide before commissioning. Confirm firmware version (recommended: V2.6.x or newer) and that the Yocto image is the official Siemens SD card image, not a custom build.
Prerequisites
| Item | Specification |
|---|---|
| Hardware | SIMATIC IOT2020 (1× Ethernet) or IOT2040 (2× Ethernet, 2× RS-232/422/485) |
| Firmware | Example image V2.6.0 or later, kernel 4.4 / 4.19 (per Siemens product support) |
| Arduino IDE | 1.8.13 or newer; 2.x is supported but 1.8.x remains the more stable reference |
| Java JRE | Required for IDE on Windows hosts (32-bit JRE for legacy 1.8.x IDE) |
| Board package | Community IOT2000 board definition URL (added via File > Preferences > Additional Board URLs) |
| Network | Static IPs in two ranges (e.g. 192.168.1.0/24 for field, 192.168.2.0/24 for cloud); TFTP/SCP permitted on management interface |
| Shielding | DIN-rail mounting, 24 V DC supply, observe EMC clearances from VFD cables |
Installing the IOT2000 Board Package
- Install Arduino IDE 1.8.x from the official Arduino distribution.
- Open File > Preferences.
- Paste the IOT2000 board URL into Additional Board Manager URLs and click OK.
- Open Tools > Board > Boards Manager, search for IOT2000, and click Install. The package installs the Intel i586 compiler toolchain, the
libArduinoCoreruntime, and the board-specific libraries:Ethernet,Ethernet1,UserLed,UserButton,RS485. - Select Tools > Board > IOT2000. Choose the correct variant: SIMATIC IOT2020 (single Ethernet) or SIMATIC IOT2040 (dual Ethernet).
- Select the serial port of the USB device channel (X60) — under Windows it appears as
COMx, under Linux as/dev/ttyACM0or/dev/ttyUSB0.
Verifying the Toolchain
Upload the standard Blink sketch, but instead of driving pin 13, drive the user LED exposed by the UserLed library. The onboard status LED (D5) confirms the runtime, the Intel Quark clocks, and the watchdog path. If the LED does not toggle, you have a toolchain or boot issue — reflash the SD card image before continuing.
Dual Ethernet Configuration (X1 / X2)
The IOT2040 exposes two GbE interfaces routed through a single multi-port MAC. In Linux, X1 maps to eth0 and X2 to eth1. The default DHCP behaviour assigns both interfaces to the same routing table, which causes asymmetric behaviour: whichever interface negotiates first wins the default route and the second IP becomes unreachable.
/etc/network/interfaces — Static Dual-IP Template
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.1.200
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 192.168.1.1 8.8.8.8
auto eth1
iface eth1 inet static
address 192.168.2.201
netmask 255.255.255.0
metric 100
The metric directive on eth1 prevents the kernel from installing two equal-cost default routes. With this layout, all upstream/cloud traffic is pinned to eth0 (X1), and eth1 (X2) is dedicated to the local PLC / Modbus segment.
Why Both IPs Bind to X1
If the Arduino sketch also calls Ethernet.config(mac, ip) and Ethernet1.config(mac, ip1), the second call is silently rejected when the user-space socket layer cannot claim a route on eth1. The ifup eth1 step must succeed before the sketch starts, or the runtime falls back to a single-interface model and assigns both IP addresses to eth0.
Arduino Sketch Pattern
#include <Ethernet.h>
#include <EthernetUdp.h>
byte mac[] = { 0xE0, 0xDC, 0xA0, 0x1B, 0xB1, 0x05 };
IPAddress ipField(192, 168, 1, 200); // bound to X1 / eth0
IPAddress ipCloud(192, 168, 2, 201); // bound to X2 / eth1
EthernetServer modbusServer(502);
void setup() {
Ethernet.begin(mac, ipField);
Ethernet1.begin(mac, ipCloud);
modbusServer.begin();
}
void loop() {
EthernetClient c1 = modbusServer.available();
if (c1) handleModbus(c1);
pumpCloudSocket(Ethernet1);
}
Verification
- From a PC on the X1 subnet, ping 192.168.1.200 — should reply.
- From a PC on the X2 subnet, ping 192.168.2.201 — should reply.
- Disconnect X1 — X2 must continue to respond to pings and TCP connections. If it does not, the default route metric is wrong: re-check the
metricdirective. - Use
ip route showover SSH to confirm only one default route exists and that it points to the X1 gateway.
systemd-networkd has overridden /etc/network/interfaces. Disable systemd-networkd with systemctl disable systemd-networkd and systemctl enable networking if you intend to use the legacy ifupdown model.Serial Channels: Serial3 (X30), Serial4 (X31), SerialUsb (X60)
| Arduino object | Hardware connector | Linux device | Default mode | Notes |
|---|---|---|---|---|
Serial |
X60 (USB Device / Micro-USB) | /dev/ttyACM0 | USB CDC | Console + sketch upload |
Serial3 |
X30 (DB9 or terminal block) | /dev/ttyS3 | RS-232 | Software-selectable RS-422 / RS-485 |
Serial4 |
X31 (DB9 or terminal block) | /dev/ttyS4 | RS-232 | Software-selectable RS-422 / RS-485 |
Use the Arduino serial API directly:
void setup() {
Serial.begin(115200); // USB device / X60
Serial3.begin(9600, SERIAL_8N1); // X30 — RS-232 default
Serial4.begin(19200, SERIAL_8N1); // X31 — RS-232 default
}
Baud rates above 115 200 baud on Serial3/Serial4 require the dedicated high-speed mode flag in the IOT2000 core; otherwise characters are lost on the Quark UART FIFO.
RS-485 and RS-422 Mode Switching
The IOT2000 Arduino core exposes hardware-mode GPIOs that drive the on-board transceiver. By default the transceiver is in RS-232 (4-wire full-duplex) mode. To use RS-485 half-duplex, RS-485 full-duplex, or RS-422, the firmware must reconfigure the transceiver and (for RS-485) drive the DE/RE pin in software or hardware.
RS-485 Half-Duplex (Most Common)
#include <RS485.h>
void setup() {
RS485.begin(9600); // configures X30 as RS-485 half-duplex
RS485.enableTermination(true); // engage 120 Ω bus termination
}
void loop() {
RS485.beginTransmission(1); // slave address 1
RS485.write("PING\n");
RS485.endTransmission();
delay(50);
}
RS-422 Full-Duplex (Point-to-Point)
#include <RS485.h>
void setup() {
RS485.beginMode(9600, RS422_FULL_DUPLEX);
RS485.enableTermination(true);
}
Termination is selectable on the fly: RS485.enableTermination(true|false) toggles the on-board 120 Ω resistor across the differential pair. Engage termination only at the two physical ends of the bus; never in the middle of a multi-drop RS-485 trunk, otherwise the resistive load sums and attenuates the signal below the EIA-485 minimum of 60 mV.
RS-485 vs RS-422 Decision Matrix
| Criterion | RS-485 (2-wire half-duplex) | RS-422 (4-wire full-duplex) |
|---|---|---|
| Max nodes on a single bus | 32 unit loads (256 with repeaters) | 1 driver, up to 10 receivers |
| Cable length @ 9600 baud | 1200 m | 1200 m |
| Direction control | DE/RE GPIO required | None — separate Tx/Rx pairs |
| Typical use on IOT2000 | Modbus RTU multi-drop fieldbus | Direct connection to a single sensor / scale / drive |
User LED and User Button Libraries
The IOT2000 core ships helper libraries to drive the onboard user interface elements. The user LED is on GPIO 13 (Arduino mapping), and the user button is on GPIO 2 with internal pull-up enabled in hardware.
#include <UserLed.h>
#include <UserButton.h>
UserLed led;
UserButton btn;
void setup() {
led.begin();
btn.begin();
}
void loop() {
if (btn.pressed()) {
led.toggle();
}
delay(10); // simple debounce
}
These helpers are convenience wrappers; both LEDs and the button can also be driven by the standard digitalRead / digitalWrite calls if you prefer to keep the dependency surface small.
Sketch Upload over LAN
When the IOT2000 is installed inside a cabinet, USB access is often impractical. The IOT2000 Arduino core supports TFTP-based deployment of the compiled ELF to the device.
- Enable the LAN uploader from the Tools menu: Tools > Programmer > IOT2000 LAN Upload.
- Set the device IP in Tools > Port > Network Port (e.g.
192.168.1.200). - Press Upload. The IDE SCPs the ELF to the gateway and triggers the Galileo runtime loader.
- Watch the LED pattern: rapid blink = upload in progress, solid = application started.
iot2000-upload disable before field deployment. Siemens does not recommend exposing the uploader across the public internet or any untrusted network.UDP remoteIP() and remotePort() — Known Regression and Workaround
The Intel Galileo Arduino core (which the IOT2000 core inherits) has a long-standing issue with the EthernetUDP::parsePacket() and remoteIP() / remotePort() API. On every received UDP datagram the methods return 255.255.255.255 and port 0, regardless of the actual sender. The bug is in the user-space socket layer that wraps the Linux UDP socket and discards the recvfrom() address information.
Workaround: Raw Linux Socket via POSIX Bridge
The cleanest fix is to bypass the Arduino EthernetUDP helper and use a POSIX recvfrom() bridge function that returns the address structure to the sketch. The IOT2000 SDK provides a header for exactly this purpose.
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int udpSock;
sockaddr_in srcAddr;
socklen_t srcLen = sizeof(srcAddr);
void setup() {
udpSock = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(5005);
addr.sin_addr.s_addr = INADDR_ANY;
bind(udpSock, (sockaddr*)&addr, sizeof(addr));
}
void loop() {
char buf[256];
int n = recvfrom(udpSock, buf, sizeof(buf), 0,
(sockaddr*)&srcAddr, &srcLen);
if (n > 0) {
Serial.print("From ");
Serial.print(inet_ntoa(srcAddr.sin_addr));
Serial.print(":");
Serial.println(ntohs(srcAddr.sin_port));
}
}
This pattern is also valid for raw TCP listeners, where the Arduino EthernetServer API correctly reports the peer address — but the same approach keeps the sketch portable across both Ethernet objects.
Troubleshooting Matrix
| Symptom | Likely root cause | Action |
|---|---|---|
| Sketch does not upload over USB | USB driver missing on Windows | Install Arduino USB CDC driver; verify /dev/ttyACM0 on Linux |
| LED does not blink after upload | Wrong board variant selected | Confirm IOT2020 vs IOT2040 in Tools > Board |
| Both IPs bound to X1 | systemd-networkd active, /etc/network/interfaces ignored | Disable systemd-networkd, restart networking |
| X2 unreachable, X1 takes two IPs | eth1 has no default route and kernel installs eth0 twice | Set metric on eth1, omit gateway on eth1 |
| UDP sender always 255.255.255.255 | parsePacket() bug inherited from Galileo core | Use raw POSIX recvfrom() bridge |
| RS-485 garbled data | Termination engaged at non-end node | Disable termination, leave only at two bus ends |
| RS-485 garbled data (long cable) | No termination at all | Enable 120 Ω termination at the two physical ends |
| Sketch upload over LAN fails | iptables default policy DROP | Allow TFTP/SCP on the management VLAN only |
| Serial3 / Serial4 returns -1 from begin() | Transceiver not powered (24 V missing) | Verify 24 V DC supply and fusing |
| Program crashes after a few hours | Watchdog not serviced in long loops | Add wdt_reset() or yield to runtime |
When to Migrate from Arduino IDE to Eclipse
The Arduino workflow is a thin IDE on top of avr-gcc-style compilation. The IOT2000 core recompiles against the Intel Quark toolchain and produces an ELF that runs under the Galileo runtime. It is intentionally simple, which means it is intentionally limited:
- No project system — multi-file sketches are managed with the legacy
inotool, which does not understand modern C++ namespaces. - Debugging is restricted to
Serial.printtrace lines. - No remote GDB, no breakpoints, no memory inspection.
- No static analysis or sanitizers out of the box.
The Eclipse workflow recommended by Siemens (Intel System Studio IoT Edition) produces a real optimised Linux binary, supports full remote debugging over Ethernet, and integrates with gdb-multiarch. The same Arduino library source files compile in Eclipse with minor adjustments to the include paths. Use Arduino for bench evaluation, switch to Eclipse for the first field-ready revision.
Cloud Connectivity
Although the IOT2000 is a Linux device, the Arduino workflow is not the natural fit for cloud integration. Microsoft publishes an official Azure IoT C SDK with a thin Arduino-style wrapper that compiles against the IOT2000 core. The library handles SAS token authentication, MQTT over TLS, and device twin state. Engineers who need a quick path from the Arduino sketch to Azure can drop the library in alongside the existing Ethernet or Ethernet1 object and reuse the same MAC address configuration. The compiled artifact remains an ELF, so the migration to Eclipse is transparent when the time comes.
Field Commissioning Checklist
- Verify SD card image version and checksum against the Siemens download portal.
- Apply static IPs and confirm
ip route showshows exactly one default route. - Ping from both subnets. Disconnect X1, confirm X2 stays alive.
- Loopback-test Serial3 and Serial4 with a null-modem cable and a terminal on the other end.
- If RS-485 is in use, walk the bus and confirm termination is engaged only at the two physical ends.
- Test the LAN uploader at least once after cabinet door closure, then disable it.
- Run the application for at least 24 h, monitoring
dmesgand the Galileo runtime log for OOM, watchdog, or socket-leak messages.
FAQ
Why do both of my static IP addresses bind to the X1 port on the SIMATIC IOT2040?
The kernel installs a default route for both eth0 and eth1 when both define a gateway. Set a metric on eth1 and remove its gateway directive so only eth0 owns the default route. Also confirm systemd-networkd is disabled — it silently overrides /etc/network/interfaces.
How do I switch the IOT2000 serial ports between RS-232, RS-422, and RS-485?
Use the RS485 library that ships with the IOT2000 board package: RS485.beginMode(baud, RS485_HALF_DUPLEX), RS485.beginMode(baud, RS422_FULL_DUPLEX), or RS485.beginMode(baud, RS232). The library drives the transceiver control pins and configures the UART in the same call.
Why does remoteIP() always return 255.255.255.255 on a received UDP packet?
This is a known regression in the Intel Galileo core inherited by the IOT2000 Arduino core. The user-space socket layer drops the recvfrom() peer address. The documented workaround is to bypass EthernetUDP and call the POSIX recvfrom() directly from the sketch.
Can I upload a sketch to the IOT2000 over the network instead of USB?
Yes. Select Tools > Programmer > IOT2000 LAN Upload in the Arduino IDE and set Tools > Port > Network Port to the device IP. The IDE SCPs the ELF and triggers the Galileo loader. Disable the LAN uploader after commissioning — it has no authentication.
Should I keep developing on the Arduino IDE or migrate to Eclipse?
Use the Arduino IDE for prototypes, education, and small bench evaluations only. For production firmware — especially anything that will be deployed with PROFINET, OPC UA, or extended field runtime — migrate to the Eclipse workflow recommended by Siemens. The Arduino library source files compile in Eclipse with minimal changes, and you gain remote GDB debugging, static analysis, and a real optimised Linux binary.