Problem Overview
A SIMATIC IOT2040 gateway runs Node-RED, and the engineer wires a 3.3 V signal into one of the Arduino-shield digital inputs. The node-red-contrib-iot2000 digital input node is installed, the debug tab reports payload: 1, and yet no downstream function ever fires. The wire is plugged, unplugged, and re-plugged; the value never leaves 1. The engineer expects rising-edge behaviour (idle low, transition high = event), the way a Raspberry Pi GPIO behaves, and concludes that the input is dead.
This is not a hardware fault and not a dead node. The IOT2040 digital input stage powers up with its internal pull-up resistor enabled, so the resting level is logic HIGH (1) at boot. The polling node only emits a message when the read value changes, and a floating or +3.3 V wire does not change that value. Triggering a function therefore requires a HIGH-to-LOW transition, or a low pulse long enough for the configured update interval to sample it.
msg.payload = 1 at all times; no change event is emitted regardless of wire state, so the function node is never reached.SIMATIC IOT2040 Hardware Architecture (Arduino Shield Side)
The IOT2040 (6ES7647-0AA00-0YA2) is an Intel Quark x1000 based IoT gateway with an Arduino-Uno-R3 compatible shield interface. The shield side exposes the pins used by node-red-contrib-iot2000 and the operating voltage of those pins is dictated by the IOREF jumper on the underside of the board. The relevant block is summarised below.
| Block | Part / Reference | Value |
|---|---|---|
| CPU | Intel Quark x1000 (x86, 400 MHz) | Single core, 32-bit |
| RAM / Flash | 1 GB DDR3 / 8 MB onboard + SD | Yocto / Example Image |
| Arduino-shield digital I/O | D0–D13 (ATmega32u4-compatible mapping on the shield connector) | 14 lines, 3.3 V or 5 V |
| Arduino-shield analog inputs | A0–A5 | 10-bit ADC |
| Shield logic level | IOREF jumper (JP1) on PCB underside | 3.3 V or 5 V |
| Digital input idle state | Internal pull-up enabled by firmware at boot | Logic HIGH = 1 |
| Default update interval | node-red-contrib-iot2000 polling timer | 1000 ms (user-configurable) |
The IOREF jumper is the first thing to verify. If the jumper is set to 5 V and the field wiring only produces 3.3 V, the line may be below the Vil threshold of the IOT2040 shield input and interpreted as indeterminate. With the jumper set to 3.3 V the Vil/Vih thresholds become compatible with 3.3 V CMOS logic and 3.3 V signals from sensors, buttons, or another MCU read cleanly.
Root Cause Analysis
Three independent causes can produce the symptom. Diagnose them in this order:
- IOREF jumper is on 5 V. The shield input stage expects 5 V CMOS levels. A 3.3 V drive lands in the indeterminate band; some firmware reads it as a stable 1, others as a stable 0, and the input never transitions cleanly.
-
Engineer is looking for a rising edge, the firmware only emits a
changeevent. Theiot2000-in-digitalnode polls the input every N ms and emits a message only whencurrent != previous. If you start at 1 and stay at 1, no message is ever sent. A floating input on the IOT2040 also reads as 1 because the internal pull-up is enabled at boot, so "unplugged" is not equivalent to "low". - Pull-up never disabled and contact-to-GND wiring not used. To get a reliable logic 0, the input must be actively driven to GND. Disconnecting the wire simply leaves the pull-up to charge the line back to 3.3 V, which is exactly what the user is observing.
function node, a switch node, or a trigger node downstream of the digital input.Step 1: Verify the IOREF Jumper
Power down the IOT2040 and remove all wiring from the Arduino-shield connector. Flip the unit over and locate the IOREF jumper (labelled JP1 in the hardware manual; the shield connector is on the top side).
- Identify the three pads marked
3V3– 5V –GND. - Place the jumper across the
3V3centre pad and the corresponding 3.3 V pad. The shield digital I/O will now use 3.3 V logic levels. - Reassemble the unit and power it back on. The shield side is now Vih ≥ 2.0 V, Vil ≤ 0.8 V, which matches 3.3 V CMOS drivers.
Step 2: Wire the Digital Input Correctly
The internal pull-up on the IOT2040 shield input is enabled by firmware, so the resting level is 3.3 V. To create a HIGH-to-LOW transition you must actively short the input to GND. The recommended wiring is shown below.
+3.3 V (Arduino shield "IOREF=3V3" pin)
|
[ 10 kOhm external pull-up ] <-- only required if you disable the internal pull-up
|
+---- IOT2040 shield D7 ----+ (chosen for clarity; D2..D12 behave identically)
|
[ momentary N.O. push button ]
|
GND (Arduino shield GND pin)
Operation:
- Button released: D7 is pulled to 3.3 V through the internal pull-up →
payload = 1. - Button pressed: D7 is shorted to GND →
payload = 0. - Node emits a message only on the transition 1→0 (or 0→1, if your button release is what you want to detect).
If you wire a sensor with an open-collector output, wire the output transistor between the shield digital input and GND, and leave the internal pull-up enabled. Most proximity, limit-switch, and opto-isolated sensor outputs are wired exactly this way.
Step 3: Install and Configure node-red-contrib-iot2000
The Node-RED palette on the IOT2040 example image ships without the IOT2000 nodes by default. Install the official package from the Siemens-Support GitHub release so the polling interval can be set explicitly.
# on the IOT2040 shell, via SSH or serial console
cd ~/.node-red
npm install node-red-contrib-iot2000
node-red-restart
After restart, drag an iot2000 in (digital) node from the palette onto the flow. The relevant configuration parameters are:
| Parameter | Recommended | Notes |
|---|---|---|
| Pin | D7 (or D2–D12) | Avoid D0/D1 if you also use the shield UART; avoid D13 if you need the onboard LED. |
| Direction | Input | Set explicitly even though the node only supports input. |
| Update interval (ms) | 100 | Lower = lower latency, higher CPU; below 20 ms the Quark is noticeably loaded. |
| Initial value | 1 | Matches the pull-up default. Pre-seeds the change detector. |
node-red-contrib-iot2000 published before mid-2018 require a manual inject pulse on a separate trigger node to refresh the value. Always pull the latest release from the Siemens-Support GitHub; older revisions silently drop the 1→0 transition if the interval is set too long.Step 4: Build the Trigger Flow
The minimum working flow is digital input → switch → function → debug. The switch node filters the transition you care about, and the function node holds your application logic.
[ iot2000-in D7 ] --> [ switch: payload === 0 ] --> [ function: onPress ] --> [ debug ]
|
+-----> [ debug: "raw level" ] (temporarily, for verification)
Switch node configuration:
- Property:
msg.payload - Type:
== - Value:
0
This routes only the HIGH-to-LOW transition into the function. The complementary transition (release, 0→1) can be routed to a second function if you need release-edge semantics.
Function Node Code
The Node-RED function node receives a JavaScript object called msg. A minimal press handler looks like this:
// Detect a single button press on the IOT2040 shield digital input
// msg.payload is 0 when the input is shorted to GND, 1 when released.
let pressCount = context.get('pressCount') || 0;
let lastEdge = context.get('lastEdge') || Date.now();
if (msg.payload === 0) {
const now = Date.now();
const debounceMs = 50; // match to the polling interval + 10% margin
if (now - lastEdge > debounceMs) {
pressCount += 1;
context.set('pressCount', pressCount);
context.set('lastEdge', now);
// Application payload: do something useful here
msg.payload = { event: 'press', count: pressCount, t: now };
msg.topic = 'iot2040/d7';
return msg;
} else {
return null; // debounced, swallow
}
}
return null; // ignore releases
The 50 ms debounce matches the 100 ms polling interval and prevents the function from firing twice on a single press if the contact bounces across the sample point. If you set the polling interval to 20 ms, drop the debounce to 25 ms.
Step 5: Verify the Flow
- Deploy the flow. The right-hand debug tab should show
payload: 1on the "raw level" branch within one update interval. - Press the push button (or short the input to GND with a jumper lead). The debug tab should show two messages almost simultaneously:
payload: 0on the raw branch andpayload: { event: 'press', count: 1, t: ... }on the function branch. - Release the button. The raw branch emits
payload: 1; the function branch is silent because of the earlyreturn nullfor releases. - Press the button a second time.
countshould increment to 2.
If step 2 produces no payload: 0 message, the input is not actually transitioning. Re-check the IOREF jumper and the wiring, and verify the shield pin number matches the configuration in the node (D7 on the connector, D7 in the node config).
SVG: Trigger State Machine
Edge Cases and Field Pitfalls
| Symptom | Likely cause | Fix |
|---|---|---|
| Debug always shows 1, even with wire shorted to GND | IOREF jumper on 5 V, signal below Vil threshold | Move jumper to 3V3 position and power-cycle |
| Debug shows 0 the moment the wire is unplugged | Input is floating, internal pull-up not loaded yet | Re-check Node-RED startup order; the firmware enables pull-ups at boot, before Node-RED starts |
| Function fires multiple times on a single press | Polling interval shorter than contact bounce | Increase debounce in the function node to 1.5× the polling interval |
| Function never fires, but debug shows 0 then 1 | Switch node misconfigured (e.g. !== instead of ==) |
Use switch with type ==, value 0, or implement the comparison in the function |
| No messages on the debug tab at all | Old node-red-contrib-iot2000 revision that requires an external trigger pulse |
Update the node package from the Siemens-Support GitHub release |
| Latency is > 1 s | Update interval left at default 1000 ms | Set Update interval to 100 ms for human-perceptible buttons, 20 ms for fast sensors |
| Function fires once after deploy, then never again | Initial value not pre-seeded to 1; first transition is treated as initial state | Set Initial value = 1 in the node config |
Alternatives if the Shield Input Is Unavailable
If the IOT2040 Arduino-shield connector is already populated with a stackable shield and you cannot move the IOREF jumper, or if the wire run is too long for the shield input to read reliably, use one of the two on-board RS-232/422/485 ports with an external dry-contact-to-serial converter (e.g. a Moxa ioLogik-style module) and parse the bytes in a function node. Alternatively, install a USB-to-IO bridge on the USB 2.0 port and use the node-red-contrib-modbus or node-red-contrib-opcua nodes; the IOT2040 supports both server and client roles for Modbus TCP and OPC UA out of the box.
Safety and Wiring Notes
- The Arduino-shield connector on the IOT2040 is not isolated from the gateway's 24 V supply. A field wiring fault that injects mains voltage onto a shield pin will destroy the Quark SoC. Use opto-isolated sensor interfaces when wiring to plant-side contacts.
- Do not source more than 20 mA from a shield 3.3 V pin. The on-board LDO is shared with the Quark core and will brown-out the CPU if overloaded.
- Always power-cycle the IOT2040 after changing the IOREF jumper; the firmware samples the level once at boot.
- Use shielded cable for runs longer than 3 m in electrically noisy cabinets. The shield input is a high-impedance CMOS line and couples capacitively to nearby relay coils and VFD cables.
Document References
- Siemens Industry Online Support – Access input pins, user button and multi-colour user LED from Node-RED
- Siemens Industry Online Support – Change the operating voltage of the Arduino shield on the IOT2040
- Node-RED official documentation – Writing Functions
FAQ
Why does the IOT2040 digital input read 1 even with nothing connected?
The shield digital inputs power up with their internal pull-up resistors enabled, so the resting level is 3.3 V (logic 1). To get a reliable 0 you must short the input to GND, not simply disconnect the wire.
How do I select 3.3 V or 5 V logic on the IOT2040 shield?
Move the IOREF jumper (JP1) on the underside of the PCB to the 3V3 or 5V position, then power-cycle the unit. The firmware reads the level once at boot; restarting Node-RED alone is not sufficient.
What polling interval should I use for the iot2000-in digital node?
100 ms is a good default for human-operated push buttons. Drop to 20 ms for fast sensor signals, but expect higher CPU load on the Quark x1000. Always debounce the input in the downstream function node with a margin of 1.5× the polling interval.
Can the iot2000-in node detect a rising edge instead of a falling edge?
No. The node only reports the current level and emits a message on change. Implement rising-edge detection with a function node that compares the incoming msg.payload against the previous value stored in context.
My function never fires even though the debug tab shows the level changing. What is wrong?
Most often the switch or function node is filtering on the wrong value. Confirm the switch property is msg.payload with type == and value 0, and that the function returns msg (not null) on the edge you care about. Verify the wiring topology matches the state machine in this article.