Estimated Time: 45 minutes
Target Board Variant: Arduino Nano ESP32 (ABX00092)
Building an infrared camera Arduino project is one of the most rewarding sensor builds you can tackle, but it is also one of the most common sources of I2C bus failures on the workbench. While the older 8x8 AMG8833 sensor is simpler, it lacks the resolution needed for practical thermal imaging. The modern standard is the 32x24 MLX90640 Far-Infrared Thermal Sensor. However, its high data throughput and strict I2C clock-stretching requirements routinely brick builds on legacy 8-bit boards.
This guide walks you through wiring the MLX90640 to the Arduino Nano ESP32, provides production-ready code with robust error handling, and serves as a definitive debugging manual for the infamous I2C NACK errors that plague thermal array projects.
Parts List & Spec Sheet
Do not substitute the microcontroller without reviewing the debugging section below. The MLX90640 requires a microcontroller with at least 3KB of free SRAM (for the 768-float frame buffer) and native I2C clock-stretching support.
| Component | Exact Variant / Model | 2026 Pricing (Approx) |
|---|---|---|
| Microcontroller | Arduino Nano ESP32 (ABX00092) | $21.00 |
| Thermal Sensor | Adafruit MLX90640 Breakout (PID: 4407) | $45.00 |
| Pull-up Resistors | 2.2kΩ 1/4W Through-Hole (x2) | $0.10 |
| Logic Level (If using 5V board) | BSS138 Bidirectional Level Shifter | $2.50 |
Pin Mapping & Wiring Steps
The Arduino Nano ESP32 operates at 3.3V logic, which perfectly matches the MLX90640's native voltage. If you are adapting this to a 5V board (like the Uno R4 WiFi), you must insert a BSS138 level shifter on the SDA and SCL lines to prevent frying the sensor's I2C transceiver.
Wiring Table
| MLX90640 Pin | Arduino Nano ESP32 Pin | Notes |
|---|---|---|
| VIN | 3V3 | Do not use 5V on the Nano ESP32's 3V3 out if drawing >150mA. |
| GND | GND | Ensure a common ground plane. |
| SDA | A4 (SDA) | Add 2.2kΩ pull-up to 3V3. |
| SCL | A5 (SCL) | Add 2.2kΩ pull-up to 3V3. |
Compilable Code (Arduino Nano ESP32)
This code targets the Arduino Nano ESP32 using the Arduino core in the IDE. It requires the Adafruit_MLX90640 library. The code includes explicit pin definitions, I2C clock configuration, and robust error handling to catch frame-read failures before they crash the serial output.
#include <Wire.h>
#include <Adafruit_MLX90640.h>
// Pin definitions for Arduino Nano ESP32 (Hardware I2C)
#define I2C_SDA A4
#define I2C_SCL A5
// Sensor I2C Address (Default is 0x33)
#define MLX_I2C_ADDR 0x33
Adafruit_MLX90640 mlx;
float frame[32 * 24]; // 768 pixels, requires ~3KB SRAM
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("Initializing MLX90640 Thermal Camera...");
// Initialize I2C with explicit pins and 400kHz clock
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(400000);
// Allow sensor to power up and stabilize
delay(100);
if (!mlx.begin(MLX_I2C_ADDR, &Wire)) {
Serial.println("FATAL ERROR: Failed to find MLX90640 sensor!");
Serial.println("Check I2C wiring, pull-up resistors, and power.");
while (1) delay(10); // Halt execution
}
Serial.println("MLX90640 Found!");
// Set sensor to Chess pattern mode for better spatial resolution
mlx.setMode(MLX90640_CHESS);
// Set refresh rate to 16Hz (Requires 1MHz I2C to sustain without buffer overruns)
// If using 400kHz, drop this to MLX90640_2_HZ or MLX90640_4_HZ
mlx.setResolution(MLX90640_ADC_18BIT);
mlx.setRefreshRate(MLX90640_2_HZ);
Serial.println("Sensor Configured. Outputting CSV frame data...");
}
void loop() {
// Attempt to read a full frame
int readStatus = mlx.getFrame(frame);
if (readStatus != 0) {
Serial.print("ERROR: Failed to get frame data. Error code: ");
Serial.println(readStatus);
Serial.println("I2C bus likely stalled or NACK received. Resetting I2C...");
Wire.end();
delay(50);
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(400000);
delay(100);
return; // Skip this loop iteration
}
// Output the 32x24 grid as comma-separated values
for (int y = 0; y < 24; y++) {
for (int x = 0; x < 32; x++) {
Serial.print(frame[y * 32 + x], 2);
if (x < 31) Serial.print(",");
}
Serial.println();
}
Serial.println("---END_FRAME---");
// Delay to match 2Hz refresh rate
delay(500);
}
Debugging: I2C NACK & Frame Failures
Thermal arrays are notoriously finicky on the I2C bus. If your serial monitor halts or throws errors, follow this diagnostic tree.
The Exact Error Strings
- "FATAL ERROR: Failed to find MLX90640 sensor!" (Triggered when
mlx.begin()fails). - "ERROR: Failed to get frame data. Error code: -1" (Triggered when
mlx.getFrame()times out). - Underlying Wire Error:
Wire.endTransmission()returns2(NACK on address) or3(NACK on data).
The First Three Things to Check
- Pull-Up Resistor Strength: As mentioned, the breakout's 10kΩ pull-ups are insufficient for high-speed I2C. If you are getting intermittent NACKs (Error 2) or frame timeouts, solder two 2.2kΩ resistors between the SDA/SCL lines and the 3.3V rail. This sharpens the signal rise time, which is mandatory for the MLX90640's heavy bus traffic.
- Logic Level Mismatch: The MLX90640 is strictly a 3.3V device. If you connected it to a 5V Arduino Uno R3 or Mega without a level shifter, you have likely permanently damaged the sensor's I2C transceiver. Verify the sensor isn't drawing excessive current (a sign of a fried silicon die) by measuring the VIN-GND resistance with a multimeter; it should not be a dead short.
- I2C Clock Stretching Support: The MLX90640 uses "clock stretching" to hold the SCL line low while it performs internal ADC calculations. The legacy AVR
Wirelibrary (used on the Uno R3/Mega) does not support clock stretching in hardware and will hang indefinitely. The Arduino Nano ESP32 handles this natively in hardware. If you must use an AVR board, you have to patch theWirelibrary or use a software I2C fallback.
Ranked Causes for "Failed to get frame data"
If the sensor initializes but fails to read frames mid-operation:
- Refresh Rate vs. Bus Speed Mismatch: You set the refresh rate to 16Hz or 32Hz but left the I2C clock at 100kHz or 400kHz. The bus cannot physically transfer 768 floats fast enough. Lower the refresh rate to 2Hz, or increase
Wire.setClock(1000000). - Power Supply Brownout: The MLX90640 draws up to 80mA during active measurement. If your Arduino's 3.3V regulator cannot supply this (common on cheap clones), the sensor voltage sags, causing an internal reset mid-frame. Power the sensor VIN from a dedicated 3.3V buck converter if using a low-power board.
- Capacitive Bus Loading: Long jumper wires (over 6 inches) add parasitic capacitance to the I2C lines, ruining the signal integrity. Keep I2C traces under 10cm, or use an I2C bus extender like the PCA9615.
Extending and Simplifying the Build
To Simplify: If the MLX90640's $45 price tag or I2C headaches are too much for a proof-of-concept, downgrade to the AMG8833 (Grid-EYE). It is an 8x8 sensor that costs around $15, requires only 64 floats of SRAM, and works flawlessly on legacy 5V AVRs with the standard Wire library at 100kHz. You sacrifice resolution, but gain plug-and-play reliability.
To Extend: To turn this CSV serial output into a visual thermal display, route the serial data to a Python script using matplotlib or OpenCV for real-time heatmap rendering. For a standalone display, swap the Nano ESP32 for an ESP32-S3 with a 2.8" TFT, utilizing the TFT_eSPI library to map the 768 float values directly to a 256-color RGB palette locally, bypassing the need for a host PC.
Frequently Asked Questions
Can I use a standard Arduino Uno R3 for an infrared camera project?
Technically yes, but practically no. The Uno R3 (ATmega328P) only has 2KB of total SRAM. The MLX90640 requires a 3KB buffer just to hold one frame of float data, meaning it will instantly overflow memory and crash. Furthermore, the AVR's hardware I2C peripheral lacks clock-stretching support, meaning the sensor will freeze the bus while calculating. If you must use an 8-bit AVR, you are restricted to the 8x8 AMG8833 sensor.
How do I increase the resolution of my Arduino thermal camera?
Hardware resolution is fixed by the silicon die (32x24 for the MLX90640). To achieve a higher-resolution output, you must apply software interpolation. Bicubic interpolation is the industry standard for thermal arrays. You can implement a 2D bicubic interpolation algorithm in your Python host script to scale the 32x24 grid up to 256x192 pixels, smoothing out the blocky edges while preserving the thermal gradients.
Why is my infrared camera Arduino reading room temperature for everything?
This is almost always caused by leaving the protective Kapton tape or plastic film over the sensor lens during testing. Thermal arrays do not see visible light; they measure long-wave infrared radiation (8-14 μm). Standard plastics and tapes are opaque to LWIR and will emit their own surface temperature rather than passing the target's heat. Additionally, ensure you are not pointing the sensor at highly polished metals, which have very low emissivity and will reflect ambient room heat rather than emitting their own.






