Reading MLX90614 IR Sensor on SIMATIC IOT2040 via Node-RED

David Krause17 min read
Sensor IntegrationSiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. System Overview and Data Flow

The integration described in this article captures non-contact surface temperature data from a Melexis MLX90614 infrared thermometer on the internal I²C bus of a Siemens SIMATIC IOT2040, parses the result inside Node-RED, and forwards the telemetry to MindSphere as a time series. The IOT2040 is an Intel Quark x1020 based industrial IoT gateway that ships with the SIMATIC IOT2000 SD-Card Example Image containing Node-RED, the iot2000setup tool, and the libmraa I/O library. The MLX90614 is a single-pixel thermopile sensor with integrated signal conditioning, factory-calibrated SMBus-compatible I²C interface, and an object temperature range of -70 °C to +380 °C on the BCC variant. The sensor combines a thermopile detector with an integrated ASIC; both chip and object temperature are returned in Kelvin, scaled to 0.02 K per LSB.

The key challenge addressed here is that the IOT2040's internal I²C bus is not exposed as a standard Linux /dev/i2c-N node in the default state, and Node-RED does not include a generic I²C read node targeting the Quark/Yocto stack. The node-red-contrib-mlx-sensor contribution is the supported workaround: it uses the cross-platform i2c-bus npm package and decodes the MLX90614 register map directly, returning msg.payload.ambient and msg.payload.object in degrees Celsius.

MLX90614 SMBus / I²C @ 0x5A SIMATIC IOT2040 Intel Quark x1020 Yocto Linux + Node-RED libmraa / i2c-bus Internal 26-pin header /dev/i2c-0 enabled Node-RED Flow mlx-sensor → function function → debug interval 5 s MindSphere Time Series API Asset / Aspect model SDA / SCL msg.payload HTTPS 8443

2. Prerequisites

Item Specification Notes
SIMATIC IOT2040 6ES7647-0AA00-1YA2 (or -1YA3, -0AA01) Intel Quark x1020 @ 400 MHz, 1 GB DDR3, 2× GbE, 2× USB 2.0, 1× internal Arduino-compatible 26-pin header
Example Image SIMATIC IOT2000 SD-Card Example Image V2.6.1 (or V3.x) Includes Node-RED, iot2000setup, libmraa, libupm, node.js, openssh, i2c-tools
MLX90614 module CJMCU-MLX90614, Sparkfun SEN-09570, Adafruit PID 1748, or bare MLX90614BAA 3.3 V version preferred; 5 V breakouts with on-board LDO accept 3.3 V input
Wiring 4-wire female-female DuPont, 22-26 AWG, < 100 mm Short leads stay within the SMBus 300 ns rise-time limit at 100 kHz
Pull-up resistors 2.2 kΩ to 4.7 kΩ on SDA and SCL to VCC Most MLX90614 carrier boards include the resistors; confirm with the board schematic
Network TCP/IPv4, DNS, NTP from IOT2040 to MindSphere tenant MindSphere requires NTP-skew < 60 s; configure systemd-timesyncd or ntpd
MindSphere access MindConnect Element credential, tenant URL, asset model Provisioned through the MindSphere Cockpit MindConnect page
⚠ Voltage note: The IOT2040 internal header carries 3.3 V on the 3V3 pin. The MLX90614 operates from 2.6 V to 3.6 V. Do not connect a 5 V-only MLX90614 carrier to the 3.3 V rail without confirming the on-board LDO accepts 3.3 V input; the CJMCU-MLX90614 ships with an AP2112K LDO that requires ≥ 3.0 V and works from the 3V3 pin.

3. Hardware Wiring: IOT2040 Internal Header to MLX90614

The SIMATIC IOT2040 exposes an internal 26-pin header following the Arduino Uno pinout convention. The I²C pins are located on the AREF/IOREF group of the 26-pin connector. The relevant signal pins are listed below; the SDA / SCL pair appears on the analog header at A4 / A5, which the IOT2040 firmware reassigns to the SoC's I2C0 controller.

IOT2040 26-pin pin Arduino name MLX90614 carrier pin Function
4 3V3 VCC (or VIN on CJMCU) +3.3 V supply to sensor
6 GND GND Common ground
7 GND GND Common ground (second bond)
13 A4 / SDA SDA I²C data (with pull-up to 3V3)
14 A5 / SCL SCL I²C clock (with pull-up to 3V3)
  1. Power down the IOT2040: sudo shutdown -h now.
  2. Open the enclosure and identify the 26-pin internal header near the Quark module.
  3. Connect 3V3 → VCC, GND → GND, A4 (SDA) → SDA, and A5 (SCL) → SCL using DuPont jumpers.
  4. Verify that the carrier board has 2.2 kΩ – 4.7 kΩ pull-ups on SDA and SCL tied to its VCC rail. The CJMCU-MLX90614, Sparkfun SEN-09570, and Adafruit 1748 all include these resistors.
  5. Close the enclosure, re-attach the SD card, and power up.
Bus capacitance: The total capacitance of SDA and SCL to ground must remain below 200 pF for 100 kHz operation. The IOT2040 PCB itself contributes roughly 25 pF, leaving about 175 pF of margin for the harness and the carrier. If the harness exceeds 100 mm, add an external 2.2 kΩ pull-up to 3.3 V on each line near the IOT2040 end.

4. Enabling I²C and Verifying the Sensor on the Bus

By default the IOT2000 Example Image ships with I²C and SPI disabled in the kernel device tree to free the pins for the user application. The iot2000setup tool toggles the device tree overlay.

# Connect over SSH or via the serial console
ssh root@<iot2040-ip>
iot2000setup

Inside the TUI:

  1. Select Peripherals → I2C.
  2. Toggle Enable I2C0 to Yes.
  3. Optional: toggle Enable SPI if you plan to add a display.
  4. Select Save and Reboot.

After reboot, verify that the device node exists:

root@iot2040:~# ls -l /dev/i2c-0
crw-rw---- 1 root i2c 89, 0 Jan  1 00:12 /dev/i2c-0
root@iot2040:~# dmesg | grep -i i2c
[    1.832] i2c /dev entries driver
[    2.014] i2c-designware-pci 0000:00:08.0: I2C bus registered
Tip: If /dev/i2c-0 is missing after reboot, the i2c-dev module is not loaded. Run modprobe i2c-dev and persist it via /etc/modules-load.d/i2c-dev.conf.

The IOT2000 Example Image includes i2c-tools with i2cdetect, i2cget, and i2cset. Confirm the sensor is wired correctly before touching Node-RED:

root@iot2040:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- 5a -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

0x5A is the factory default MLX90614 7-bit address. If you see -- at that position, the most common causes are:

  • Wiring error (SDA / SCL swapped, or 5 V applied to a 3.3 V sensor).
  • Missing pull-up resistors on the carrier board.
  • I²C not enabled in iot2000setup.
  • Sensor sleeping (the MLX90614 enters sleep on SCL low > 33 ms; toggle SCL to wake).

Read the raw object-temperature register to confirm a sane value:

root@iot2040:~# i2cget -y 0 0x5A 0x07 w
0x3A1A
root@iot2040:~# python3 -c "print((0x3A1A)*0.02 - 273.15)"
24.33

The MLX90614 returns temperature in 0.02 K per LSB. Decoding: 0x3A1A = 14874 × 0.02 = 297.48 K − 273.15 = 24.33 °C. A value near room temperature on a 3.3 V supply confirms the wiring, pull-ups, and address are correct. The companion register 0x06 is the chip (ambient) temperature and is normally within ±1 °C of the object temperature when the sensor faces a black-body target at ambient.

5. Installing the node-red-contrib-mlx-sensor Node

The node-red-contrib-mlx-sensor package wraps the i2c-bus npm module and exposes an mlx input node that scans the MLX90614 sub-address space (0x5A–0x5D) and emits a parsed payload. Installation is done from the IOT2040 shell:

cd ~/.node-red
npm install node-red-contrib-mlx-sensor
sudo systemctl restart node-red

Or, from the Node-RED editor:

  1. Open the editor at http://<iot2040-ip>:1880.
  2. Click the menu (☰) → Manage palette → Install.
  3. Search for node-red-contrib-mlx-sensor and click Install.
  4. Wait for the dependency tree (which includes i2c-bus and rxjs) to compile; the IOT2040 takes 30–60 s for a fresh install on the Quark core.

Verify the node appears:

root@iot2040:~/.node-red# ls node_modules | grep mlx
node-red-contrib-mlx-sensor

Restart Node-RED only — do not reboot the IOT2040 — and confirm the new node is listed in the left-hand palette under the MLX group.

Permissions: The IOT2040 example image grants the node-red systemd unit access to /dev/i2c-0 via the i2c group. If you start Node-RED manually as root for debugging, the node will work even when group membership is missing. In production, run the unit: systemctl start node-red.

6. Building the Temperature Flow

The minimal flow reads the MLX90614 every 5 s, attaches a timestamp, and pushes the value to a debug pane. Drop the following nodes onto the canvas:

  1. mlx (from the MLX palette) — set Bus to /dev/i2c-0, Address to 0x5A, Interval to 5 (seconds), and enable Read ambient + Read object.
  2. function — wrap the value into a JSON envelope with ISO timestamp and topic.
  3. debug — display the JSON in the debug tab.

Paste this into the function node:

// Build a uniform message for downstream nodes
const ts = new Date().toISOString();
msg.payload = {
    timestamp: ts,
    source: "mlx90614",
    bus: "/dev/i2c-0",
    address: 0x5A,
    ambient_c: msg.payload.ambient,
    object_c: msg.payload.object
};
msg.topic = "iot/edge/temperature";
return msg;

The expected debug output should resemble:

{
    "timestamp": "2024-03-12T14:22:08.412Z",
    "source": "mlx90614",
    "bus": "/dev/i2c-0",
    "address": 90,
    "ambient_c": 23.81,
    "object_c": 24.27
}

Save the flow and click Deploy. Confirm that the debug tab updates every 5 s and that the values are within physical reason for the target surface. If ambient_c returns NaN, the bus is not enabled or the sensor is not responding — return to Section 4.

6.1 Threshold trigger pattern

To drive a relay or GPIO from a temperature trip point, add a switch node after the function block:

// switch node property = msg.payload.object_c
// case 1: > 60   (alarm HIGH)
// case 2: < 5    (alarm LOW)

Wire the HIGH output to a rpi-gpio out node (when the flow is moved to a Raspberry Pi) or to an mraa-backed GPIO pin on the IOT2040 via the node-red-contrib-mraa contribution.

7. Publishing to MindSphere

Two options are available for forwarding the JSON envelope to MindSphere.

7.1 MindConnect Node-RED node (recommended)

Siemens provides the node-red-contrib-mindconnect package, available from the Node-RED library and the Siemens MindSphere developer portal. Install it the same way as the MLX node:

cd ~/.node-red
npm install node-red-contrib-mindconnect
sudo systemctl restart node-red

Configure the mindconnect node with the agent's MindConnect onboarding credentials (issued from the MindSphere Cockpit → MindConnect page) and map the function output to the data point definition declared in the asset's aspect type.

7.2 Generic MQTT bridge

For tenants using the MindSphere MQTT broker, add an mqtt out node to the flow:

Parameter Value
Broker tcp://<tenant>.mindsphere.io:1883 (TLS: ssl://<tenant>.mindsphere.io:8883)
Client ID Unique per IOT2040, e.g. iot2040-<serial>
Topic spBv1.0/<tenant>/DDATA/<asset>/<aspect>
QoS 1
Payload The JSON object built in Section 6
Payload encoding: MindSphere time-series ingest expects a JSON body with timestamp, value, and qualityCode. Wrap ambient_c and object_c into two separate payloads with different data point names if you want both in the same aspect. MindSphere rejects payloads > 1 MB; the MLX envelope is < 200 bytes.

8. Alternative Path: Johnny-Five with galileo-io

When the mlx-sensor node does not cover a derived use case — for example, triggering a GPIO on a temperature threshold inside the same flow — the historic workaround is the node-red-contrib-johnny-five runtime plus the galileo-io I/O plugin. This stack has two well-documented pitfalls on the IOT2040.

8.1 Install order

cd ~/.node-red
npm install node-red-contrib-johnny-five
npm install galileo-io johnny-five

On the IOT2040 (Quark x1020), the galileo-io package compiles its native binding firmata against MRAA. If the Yocto toolchain in the image is not the default, the install produces an ELIFECYCLE error and Node-RED refuses to start on the next boot:

npm ERR! [email protected] install: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! Failed at the [email protected] install script.

8.2 Recovery procedure

  1. Stop the unit: systemctl stop node-red.
  2. Roll back the partial install: cd ~/.node-red && rm -rf node_modules/galileo-io node_modules/johnny-five && npm rebuild.
  3. Reinstall with the Yocto SDK (if installed):
    source /opt/crosscompile/environment-setup-i586-poky-linux
    npm install galileo-io --target_arch=ia32 --build-from-source
    
  4. Restart: systemctl start node-red.

If the rebuild still fails (the IOT2040 2.6.1 image does not ship with the cross-compile environment), abandon the Johnny-Five path and use the mlx-sensor node as documented in Section 5. The j5-gpio-out with embedded i2cRead call is fragile because the I²C address argument is parsed as a string and decimal/hex mismatch causes silent no-ops; the dedicated mlx-sensor node accepts both decimal and hex notation and validates the PEC byte.

8.3 Direct function-node access via i2c-bus

For engineers who prefer a single function node without a contributed package, add i2c-bus as a project dependency and read the MLX90614 directly:

const i2c = require("i2c-bus");
const bus = i2c.openSync(0);

// Read 16-bit object temperature (register 0x07)
const buf = Buffer.alloc(2);
bus.readI2cBlockSync(0x5A, 0x07, 2, buf);
const raw = (buf[0] | (buf[1] << 8)) & 0x7FFF;
const objectC = raw * 0.02 - 273.15;

msg.payload = { object_c: objectC, raw: raw };
return msg;

Run npm install i2c-bus inside ~/.node-red first. This pattern avoids any Node-RED contribution and is the most resilient for long-running edge deployments.

9. Emissivity and Accuracy Considerations

The MLX90614 leaves the factory with the emissivity register (sub-address 0x02) set to 0xFFFF, which decodes to ε = 1.0. This is correct only for a perfect black-body radiator. Common materials and their emissivity values at 25 °C are listed below; the sensor can be re-calibrated to these by writing the corresponding 16-bit value to 0x02:

Material Emissivity (ε) Register value (hex) Register value (dec)
Human skin 0.98 0x3D70 15728
Wood 0.90 0x399A 14746
Concrete 0.94 0x3C28 15400
Polished aluminium 0.05 0x051B 1307
Anodised aluminium 0.77 0x3148 12616
Stainless steel (rough) 0.85 0x3666 13926
Glass 0.92 0x3AEA 15082
PCB FR4 (copper side) 0.78 0x31EB 12779

The MELXMLX90614 absolute accuracy is ±0.5 °C in the 0–50 °C object-temperature range and ±1.0 °C across the wider –70–+380 °C span. Field drift of more than 2 °C usually indicates an emissivity mismatch, not a sensor defect. Apply the value with i2cset -y 0 0x5A 0x02 0x3D70 w and verify with i2cget -y 0 0x5A 0x02 w.

Field of view: The MLX90614BAA variant has a 90° FOV; the BCC variant narrows to 35°. Make sure the target fully fills the cone at the working distance — a partial fill under-reads the temperature proportional to the unfilled area.

10. Troubleshooting Matrix

Symptom Likely cause Remediation
i2cdetect shows -- at 0x5A Wiring error, missing pull-up, or I²C not enabled in iot2000setup Re-enable I²C, check 3V3 / GND, add 2.2 kΩ pull-ups
ELIFECYCLE during galileo-io install Native binding cannot compile on Quark x86 Use mlx-sensor node, or rebuild with Yocto SDK
Node-RED crashes on every boot after install Partial node_modules tree from failed install rm -rf node_modules package-lock.json && npm install
msg.payload.ambient is NaN Sensor returned PEC error or wired reversed Try i2cdetect -y 0 to confirm presence; swap SDA / SCL
Object temperature drifts by 5 °C Emissivity set to 1.0 default; surface not at 1.0 emissivity Write 0x02 register with surface-specific ε (e.g. 0x3D70 for human skin)
MindSphere returns HTTP 401 MindConnect agent not onboarded, or token expired Re-onboard via MindSphere Cockpit; rotate the offline-license file
MindSphere returns HTTP 413 Payload exceeds 1 MB Reduce publish frequency or trim the envelope
Reading stuck at 1027 K Reading 0x06 (ambient) but interpreting as object, or sensor saturated Read 0x07 for object temperature; 0x06 is the chip temperature
Flow stops emitting after 12 h libmraa file descriptor leak in galileo-io Switch to mlx-sensor node; add a daily inject → node.restart()
Editor hangs on Deploy Flow has a node with an invalid configuration (e.g. bad hex address) Open ~/.node-red/.flows.json and validate the JSON, or deploy one node at a time
EACCES on /dev/i2c-0 Node-RED user not in the i2c group usermod -aG i2c node-red and restart the unit
Read returns 0xFFFF PEC mismatch — electrical noise or marginal pull-up Shorten harness to < 50 mm, increase pull-up strength to 2.2 kΩ

11. MLX90614 Register Map and Timing Specifications

The MLX90614 register map is documented in the Melexis datasheet for the MLX90614 family. The sub-addresses used by the mlx-sensor node are listed below.

Sub-address (hex) Name Access Default Scale Notes
0x02 Emissivity R/W 0xFFFF (ε = 1.0) 0.0001 per LSB 16-bit value; emissivity = raw × 0.0001
0x03 Config Register 1 R/W 0x0074 — Bit 7 selects the I²C address LSB
0x04 Config Register 2 R/W 0x0093 — Controls FIR / IIR filter
0x05 Device ID R 0x1004 / 0x1007 — 0x1004 = MLX90614BAA, 0x1007 = MLX90614BCC
0x06 Tambient R — 0.02 K per LSB Chip temperature; convert with (raw × 0.02) − 273.15
0x07 Tobject R — 0.02 K per LSB Object temperature; (raw × 0.02) − 273.15
0x08 / 0x09 Raw IR channel 1 / 2 R — — For diagnostic use

11.1 I²C timing

Parameter Symbol Min Typ Max Unit
SCL clock frequency fSCL 0 100 400 kHz
Bus free time between STOP and START tBUF 1.3 — — µs
Hold time (repeated) START tHD:STA 0.6 — — µs
LOW period of SCL tLOW 1.3 — — µs
HIGH period of SCL tHIGH 0.6 — — µs
Setup time for repeated START tSU:STA 0.6 — — µs
Data hold time tHD:DAT 0 — — ns
Data setup time tSU:DAT 100 — — ns
Rise time SDA / SCL tR 20 — 300 ns
Fall time SDA / SCL tF 20 — 300 ns
STOP setup time tSU:STO 0.6 — — µs
Conversion time (refresh) tCONV — 100 — ms

The MLX90614 refreshes its internal measurement at approximately 10 Hz. Polling faster than this is wasteful; the recommended interval in Section 6 is 100 ms or longer. The IOT2040's internal pull-ups on the IOT2000 example image are too weak (10 kΩ) for reliable 100 kHz communication on bus runs longer than 50 mm; rely on the carrier board's pull-ups and ensure that total bus capacitance stays below 200 pF.

11.2 Power consumption

Mode Current (typ) Current (max)
Active (continuous measurement) 1.5 mA 2.5 mA
Sleep (SCL held low > 33 ms) 2 µA 10 µA
Step / single measurement 2.5 mA peak 3.0 mA

11.3 Verification checklist

  1. i2cdetect -y 0 returns 5a at 0x5A.
  2. Node-RED palette lists the mlx node under the MLX group.
  3. Debug tab emits one message every 5 s with non-NaN ambient_c and object_c.
  4. Touching the sensor aperture with a finger raises object_c within 2 s.
  5. MindSphere Cockpit shows the time series MLX90614.ambient and MLX90614.object updating in real time.
  6. Service restarts cleanly: systemctl restart node-red && sleep 10 && systemctl status node-red shows active (running).

12. Frequently Asked Questions

What is the default I²C address of the MLX90614, and can I change it?

The factory default 7-bit address is 0x5A (decimal 90). The MLX90614 also supports 0x5B, 0x5C, and 0x5D by writing the desired LSB to configuration register 1 (sub-address 0x03) and then issuing the dedicated MLX90614 Change I²C Address command. The new address is committed only after a power cycle. In the mlx-sensor node, set Address to the new hex value and redeploy — no physical board modification is required.

Why does Node-RED fail to start after I install node-red-contrib-johnny-five with galileo-io?

The galileo-io package is targeted at the Intel Galileo and Edison boards and bundles a native binding that fails to compile against the Quark x1020's Yocto Linux in the IOT2040 V2.6.1 image. The npm install step ends with an ELIFECYCLE error from node-gyp rebuild, and the partial node_modules tree prevents Node-RED from booting. Run rm -rf node_modules/galileo-io node_modules/johnny-five, restart Node-RED, and use the node-red-contrib-mlx-sensor package instead, which uses the cross-platform i2c-bus library and does not require native compilation.

How do I read the raw register values without Node-RED?

Use i2cget -y 0 0x5A 0x07 w from the IOT2040 shell to read the 16-bit object temperature; convert with (raw & 0x7FFF) × 0.02 − 273.15 to get degrees Celsius. The mlx-sensor node wraps this command and applies the LSB-sign correction automatically. If the read returns 0xFFFF, the PEC byte failed — reduce the I²C clock to 50 kHz or shorten the wiring to bring total capacitance below 100 pF.

Can I run this same setup on a SIMATIC IOT2050 instead of the IOT2040?

Yes, with two adjustments. The IOT2050 (6ES7647-0BA00-0YA2) is an ARM Cortex-A53 (TI AM6528) platform; the I²C bus is exposed as /dev/i2c-1 on the internal header rather than /dev/i2c-0, and the device-tree overlay is toggled through iot2050setup instead of iot2000setup. Update the Bus field in the mlx-sensor node to /dev/i2c-1 and verify with i2cdetect -y 1. The wiring and the MindSphere forwarding steps are identical.

How do I publish the temperature to MindSphere securely?

Use the node-red-contrib-mindconnect node over TLS 1.2 to the MindConnect Element endpoint on port 8443. The agent must be onboarded once via the MindSphere Cockpit by uploading the agent's PEM certificate and assigning the tenant. After onboarding, the node automatically rotates the short-lived bearer token; the IOT2040 system clock must be within 60 s of NTP, which is enforced by the systemd-timesyncd service started at boot on image V2.6.1 and later. Do not disable TLS — MindSphere rejects plain HTTP publishes.

Back to blog