The phrase micropython for arduino is one of the most common search traps in the embedded world. If you are trying to flash MicroPython onto a classic Arduino Uno or Nano (ATmega328P), stop right there: it will not work. MicroPython requires a minimum of ~256KB of Flash and ~32KB of SRAM to run its interpreter. The classic AVR-based Arduinos have 32KB of Flash and a mere 2KB of SRAM. They are strictly C/C++ territory.

However, the modern Arduino lineup includes powerful ARM and RISC-V boards that run MicroPython natively. This guide targets the Arduino Nano ESP32 (ABX00092), which pairs the ESP32-S3 dual-core 240MHz MCU with the familiar Nano footprint, giving you 8MB of Flash and 512KB of SRAM. We will build a robust I2C OLED and 1-Wire temperature dashboard, map the exact pins, and debug the specific I2C errors that plague beginners.

Hardware Compatibility: Which Arduino Boards Run MicroPython?

Before ordering parts, verify your board's silicon. Here is the definitive compatibility matrix for the current Arduino ecosystem. This table assumes you are using the official MicroPython builds, not third-party CircuitPython forks.

Board Variant MCU Core Flash / SRAM MicroPython Support Approx. Price (2026)
Uno R3 / Nano (Classic) ATmega328P (AVR) 32KB / 2KB No (Use C++) $15 - $22
Nano ESP32 ESP32-S3 (Xtensa) 8MB / 512KB Yes (Native, Recommended) $19 - $24
Nano RP2040 Connect RP2040 (ARM M0+) 2MB / 264KB Yes (Native) $22 - $27
Portenta H7 STM32H747 (ARM M7) 2MB / 1MB Yes (OpenMV Port) $95 - $110
Uno R4 WiFi RA4M1 (ARM M4) 256KB / 32KB Edge Case (RAM limited) $25 - $30
Bench Note: The Uno R4 WiFi technically has enough flash, but its 32KB SRAM is the bare minimum for MicroPython. You will hit memory allocation errors (MemoryError) quickly if you load Wi-Fi stacks alongside display buffers. Stick to the Nano ESP32 or RP2040 for serious projects.

Project Build: Nano ESP32 Environmental Dashboard

We are building a localized temperature monitor that reads a waterproof DS18B20 1-Wire sensor and outputs the data to an SSD1306 128x64 I2C OLED. This covers both major serial protocols you will use in embedded Python.

Parts List & Exact Variants

  • MCU: Arduino Nano ESP32 (SKU: ABX00092)
  • Display: SSD1306 128x64 I2C OLED (Adafruit 326 or generic equivalent with 4 pins: GND, VCC, SCL, SDA)
  • Sensor: DS18B20 Waterproof Probe (Adafruit 381)
  • Resistor: 4.7kΩ 1/4W through-hole (for 1-Wire pull-up)
  • Resistors: 2.2kΩ x2 (optional, for I2C pull-ups if using long wires)
  • Prototyping: 830-point breadboard, 22 AWG solid core jumper wires

Pin Mapping Table

The Nano ESP32 uses an ESP32-S3 under the hood. The silkscreen labels (A4, A5, D2) map to specific internal GPIOs. MicroPython's machine.Pin requires the internal GPIO numbers or the board-specific constants. We will use the GPIO numbers for maximum compatibility across ESP32-S3 builds.

Nano ESP32 Silkscreen Internal GPIO Component Function
A4 GPIO 5 SSD1306 OLED I2C SDA
A5 GPIO 6 SSD1306 OLED I2C SCL
D2 GPIO 2 DS18B20 1-Wire Data
3V3 N/A Both 3.3V Power
GND N/A Both Common Ground

Wiring Steps

  1. Power Rails: Connect the Nano ESP32 3V3 pin to the red breadboard rail and GND to the blue rail. Warning: Do not feed 5V into the Nano ESP32 I/O pins; it is strictly a 3.3V logic device.
  2. OLED I2C: Wire OLED VCC to 3V3, GND to GND. Connect SDA to A4 (GPIO5) and SCL to A5 (GPIO6).
  3. DS18B20 1-Wire: Connect the Red wire to 3V3, Black wire to GND. Connect the Yellow (Data) wire to D2 (GPIO2).
  4. Pull-up Resistor: Place the 4.7kΩ resistor between the Yellow data wire and the 3V3 rail. Without this, the 1-Wire bus will float and throw CRC errors.

The Code: Compilable MicroPython with Error Handling

This script targets the Arduino Nano ESP32 running the official MicroPython ESP32-S3 build. It includes hardware initialization, a fallback for missing sensor ROMs, and try/except blocks to prevent silent reboots on I2C lockups.

Note: Ensure you have uploaded the standard ssd1306.py driver to your board's root directory via Thonny or mpremote before running this.


import time
import machine
import onewire
import ds18x20
from machine import Pin, I2C
import ssd1306

# --- PIN DEFINITIONS (Nano ESP32) ---
I2C_SDA = Pin(5)  # Silkscreen A4
I2C_SCL = Pin(6)  # Silkscreen A5
ONE_WIRE_PIN = Pin(2)  # Silkscreen D2

# --- HARDWARE INITIALIZATION ---
try:
    i2c = I2C(0, scl=I2C_SCL, sda=I2C_SDA, freq=400000)
    # Scan for I2C devices
    devices = i2c.scan()
    if not devices:
        raise OSError('No I2C devices found')
    
    # Initialize OLED (Assuming standard 128x64 at address 0x3C)
    oled = ssd1306.SSD1306_I2C(0x3C, i2c, 128, 64)
    oled.fill(0)
    oled.text('System OK', 0, 0)
    oled.show()
except OSError as e:
    print(f'FATAL I2C ERROR: {e}')
    # Blink onboard LED to indicate hardware fault
    led = Pin(48, Pin.OUT) # Nano ESP32 RGB LED Green channel pin
    while True:
        led.value(1)
        time.sleep(0.1)
        led.value(0)
        time.sleep(0.1)
except ImportError:
    print('FATAL: ssd1306.py driver missing from root directory.')
    machine.reset()

# Initialize 1-Wire DS18B20
ow = onewire.OneWire(ONE_WIRE_PIN)
ds_sensor = ds18x20.DS18X20(ow)
roms = ds_sensor.scan()

if not roms:
    oled.fill(0)
    oled.text('WARN: No DS18B20', 0, 20)
    oled.show()
    print('Warning: No 1-Wire sensor detected. Check 4.7k pull-up.')
else:
    print(f'Found DS18B20 ROMs: {roms}')

# --- MAIN LOOP ---
while True:
    try:
        ds_sensor.convert_temp()
        time.sleep_ms(750) # Wait for conversion (max 750ms for 12-bit)
        
        if roms:
            temp_c = ds_sensor.read_temp(roms[0])
            temp_f = (temp_c * 9/5) + 32
            
            # Update OLED
            oled.fill(0)
            oled.text('Env Dashboard', 0, 0)
            oled.text(f'Temp: {temp_c:.1f} C', 0, 20)
            oled.text(f'Temp: {temp_f:.1f} F', 0, 35)
            oled.show()
            print(f'Temp: {temp_c:.2f}C')
        else:
            # Fallback display if sensor missing
            oled.fill(0)
            oled.text('Sensor Offline', 0, 20)
            oled.show()
            
    except Exception as e:
        print(f'Loop Error: {e}')
        oled.fill(0)
        oled.text('Read Error', 0, 20)
        oled.show()
    
    time.sleep(2)

Debugging: Exact Errors and the "First Three" Checks

When moving from C++ (Arduino IDE) to MicroPython, the error messages change from compiler warnings to runtime exceptions. Here is how to handle the most common failures.

Exact Error Strings and Ranked Causes

Error 1: OSError: [Errno 19] No such device
This occurs during i2c.scan() or ssd1306.SSD1306_I2C() initialization. It means the ESP32-S3 sent a clock pulse but received no ACKnowledge (ACK) bit back.

  • Cause 1 (80%): SDA and SCL are swapped. The Nano ESP32 silkscreen can be confusing; verify A4 is SDA and A5 is SCL.
  • Cause 2 (15%): Missing I2C pull-up resistors. While many cheap OLEDs have 10kΩ pull-ups onboard, they are often too weak for 400kHz operation. Add external 2.2kΩ pull-ups to 3V3.
  • Cause 3 (5%): The OLED is dead or operates at 5V logic and has fried the 3.3V I2C bus.

Error 2: ValueError: bad scan or OSError: [Errno 110] ETIMEDOUT on 1-Wire
This happens when ds_sensor.scan() or convert_temp() fails.

  • Cause 1 (90%): Missing or incorrect 4.7kΩ pull-up resistor on the data line. 1-Wire relies on an open-drain architecture; without the pull-up, the line stays low.
  • Cause 2 (10%): Parasitic power mode wiring error. If using a 3-wire probe, ensure Red is 3V3, Black is GND, Yellow is Data. Swapping Red and Black will instantly destroy the sensor's internal diode.

The First Three Things to Check When It Fails

If your script crashes on boot, do not rewrite the code. Execute this physical checklist:

  1. Run a Bare I2C Scanner: Strip the code down to just i2c = I2C(0, scl=Pin(6), sda=Pin(5)) and print(i2c.scan()). If it returns an empty list [], your issue is 100% physical wiring or a dead module.
  2. Measure the Rails: Put your multimeter in DC Voltage mode. Probe the breadboard 3V3 rail. It must read between 3.25V and 3.35V. If it reads 5V, you are plugged into the VUSB pin, and you are actively destroying the ESP32-S3 I/O matrix.
  3. Check the Logic Levels: The Arduino Nano ESP32 is a 3.3V device. If you are using a 5V I2C display (like some older Adafruit LCD shields), you must use a bidirectional logic level converter (like the BSS138 breakout). Direct connection will cause intermittent lockups and eventual silicon damage.

Extending and Simplifying the Build

How to Simplify

If you are just learning MicroPython syntax and the I2C/1-Wire bus is giving you grief, drop the OLED and the DS18B20. Replace the sensor with the ESP32-S3's internal temperature sensor (available in newer MicroPython builds) or simply read the internal Hall Effect sensor. Replace the OLED output with standard print() statements to the REPL. This isolates software logic from hardware bus capacitance issues.

How to Extend

Once the baseline dashboard is stable, the Nano ESP32's Wi-Fi stack is the logical next step.

  • Add MQTT Publishing: Import the umqtt.simple library. Connect to your local Wi-Fi and publish the temp_c variable to a Mosquitto broker topic like home/sensors/nano_esp32/temp. Wrap the Wi-Fi connection in a try/except block with a 5-second timeout to prevent the board from hanging if the router is offline.
  • Add Deep Sleep: If running on a battery, use machine.deepsleep(60000) at the end of the loop. The ESP32-S3 drops its current draw from ~45mA to roughly 10µA in deep sleep, extending a 2000mAh LiPo runtime from 2 days to over 6 months.

For official firmware downloads and pinout diagrams, always refer to the MicroPython ESP32-S3 download page and the Arduino Nano ESP32 hardware documentation. Local electrical codes do not apply to 3.3V breadboard prototyping, but standard ESD (Electrostatic Discharge) precautions should be observed when handling the bare Nano ESP32 module outside of its anti-static bag.