Asking "what can you do with Arduino" in 2026 is like asking what you can do with a computer. The ecosystem has fractured far beyond the classic Uno R3 blinking an LED. Today, the Arduino portfolio includes boards with dual-core 240MHz processors, native motor drivers, and hardware-accelerated cryptography. The real question is not what the platform can do, but which specific board solves your exact engineering problem without overcomplicating the build.

This guide cuts through the noise with a decision matrix to match your project goals to the right hardware, followed by a complete, bench-tested build: an IoT environmental data logger using MQTT. We will cover the exact wiring, compilable code with robust error handling, and the specific debugging steps when the serial monitor throws a fit.

The Decision Matrix: Matching Use-Cases to Hardware

Before buying parts, run your project requirements through this decision path. Do not default to the Arduino Uno R4 Minima for everything; it lacks native WiFi and will force you to add bulky shields for IoT tasks.

If your primary goal is... Buy this exact board Part Number Why this wins
High-speed motor control or robotics Arduino Portenta Machine Control ABX00055 Native 24V PLC-style I/O, integrated motor drivers, no external H-bridges needed.
Audio DSP or machine learning vision Arduino GIGA R1 WiFi ABX00063 STM32H7 dual-core (480MHz), 76 GPIO pins, hardware FPU for floating-point math.
Battery-powered IoT sensor nodes Arduino Nano ESP32 ABX00075 ESP32-S3 chip in a Nano footprint. Native WiFi/BLE, deep sleep current under 10µA.
Simple 5V logic learning or basic relays Arduino Uno R4 Minima ABX00080 Renesas RA4M1 32-bit ARM, 5V tolerant I/O, drop-in compatible with legacy shields.
The Default Pick: If you are building a connected sensor, smart home node, or data logger, terminate your decision here and buy the Arduino Nano ESP32 (ABX00075). It bridges the gap between the physical breadboard-friendly Nano form factor and the high-power ESP32-S3 silicon.

Project Build: IoT Environmental Logger (MQTT over WiFi)

To demonstrate what you can do with the Nano ESP32, we are building a sensor node that reads temperature, humidity, and barometric pressure, then publishes it to an MQTT broker. We are skipping the DHT11 sensor commonly found in beginner kits; its 8-bit resolution and 2-second polling rate are useless for real data logging. Instead, we use the Bosch BME280, which communicates over I2C and provides 20-bit temperature resolution.

Parts List & Specifications

  • Microcontroller: Arduino Nano ESP32 (Part: ABX00075) — $21.00
  • Sensor: Adafruit BME280 I2C/SPI Breakout (PID: 2652) — $9.95
  • Wiring: 22 AWG solid-core hookup wire (stranded will fray in the Nano's breadboard-friendly headers)
  • Passives: Two 10kΩ pull-up resistors (for I2C SDA/SCL lines if your breakout lacks them; the Adafruit PID 2652 has them built-in, so they are optional here but good bench practice to have on hand).

Pin Mapping Table

The Nano ESP32 uses the ESP32-S3, which allows flexible I2C pin assignment. However, to maintain compatibility with standard Nano shields and keep the code predictable, we map to the traditional analog pins.

Nano ESP32 Pin ESP32-S3 GPIO BME280 Breakout Pin Function
A4 GPIO 17 SDI / SDA I2C Data
A5 GPIO 18 SCK / SCL I2C Clock
3V3 N/A VIN / 3Vo 3.3V Power (Do NOT use 5V)
GND N/A GND Common Ground
Voltage Warning: The BME280 is strictly a 3.3V device. Feeding 5V into the VIN pin of the Adafruit breakout will fry the internal pressure membrane and the I2C level shifters. Verify your multimeter reads 3.2V–3.4V on the Nano's 3V3 pin before connecting the sensor.

Complete Compilable Code with Error Handling

This code targets the Arduino Nano ESP32 using the Arduino IDE 2.x with the ESP32 board package installed. It requires the Adafruit BME280 Library and the ArduinoMqttClient library (by Arduino). Copy this directly into your IDE.


#include <Wire.h>
#include <WiFi.h>
#include <ArduinoMqttClient.h>
#include <Adafruit_BME280.h>

// --- PIN DEFINITIONS ---
#define I2C_SDA_PIN A4
#define I2C_SCL_PIN A5
#define STATUS_LED_PIN LED_BUILTIN

// --- NETWORK & MQTT CONFIG ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_broker = "broker.hivemq.com"; // Public test broker
const int mqtt_port = 1883;
const char* mqtt_topic_temp = "flux/lab/temp";
const char* mqtt_topic_hum = "flux/lab/humidity";

// --- OBJECT INSTANTIATION ---
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
Adafruit_BME280 bme;

// --- TIMING VARIABLES ---
unsigned long lastPublishTime = 0;
const unsigned long publishInterval = 10000; // 10 seconds

void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(10); } // Wait for serial port
  
  pinMode(STATUS_LED_PIN, OUTPUT);
  digitalWrite(STATUS_LED_PIN, LOW);

  // Initialize I2C with explicit pin mapping for Nano ESP32
  Wire.setSDA(I2C_SDA_PIN);
  Wire.setSCL(I2C_SCL_PIN);
  Wire.begin();

  // Initialize BME280 Sensor
  unsigned status = bme.begin(0x77, &Wire); // Adafruit breakout defaults to 0x77
  if (!status) {
    Serial.println("FATAL: BME280 init failed! Check I2C wiring and address.");
    while (1) { 
      digitalWrite(STATUS_LED_PIN, HIGH); delay(100); 
      digitalWrite(STATUS_LED_PIN, LOW); delay(100); 
    }
  }

  // Connect to WiFi
  Serial.print("Connecting to WiFi SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  
  int wifi_attempts = 0;
  while (WiFi.status() != WL_CONNECTED && wifi_attempts < 40) {
    delay(500);
    Serial.print(".");
    wifi_attempts++;
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nFATAL: WiFi connection failed. Check credentials.");
    while (1) { delay(1000); }
  }
  Serial.print("\nConnected! IP: ");
  Serial.println(WiFi.localIP());

  // Connect to MQTT Broker
  Serial.print("Connecting to MQTT broker...");
  mqttClient.setId("NanoESP32_Lab_01");
  
  if (!mqttClient.connect(mqtt_broker, mqtt_port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());
    while (1) { delay(1000); }
  }
  Serial.println("connected.");
  digitalWrite(STATUS_LED_PIN, HIGH); // Solid LED indicates ready state
}

void loop() {
  // Keep MQTT connection alive
  mqttClient.poll();

  unsigned long currentMillis = millis();
  if (currentMillis - lastPublishTime >= publishInterval) {
    lastPublishTime = currentMillis;
    
    float tempC = bme.readTemperature();
    float humidity = bme.readHumidity();
    
    // Publish Temperature
    mqttClient.beginMessage(mqtt_topic_temp);
    mqttClient.print(tempC, 2);
    mqttClient.endMessage();
    
    // Publish Humidity
    mqttClient.beginMessage(mqtt_topic_hum);
    mqttClient.print(humidity, 2);
    mqttClient.endMessage();
    
    Serial.printf("Published -> Temp: %.2f C, Hum: %.2f %%\n", tempC, humidity);
    
    // Blink LED to indicate successful publish
    digitalWrite(STATUS_LED_PIN, LOW);
    delay(50);
    digitalWrite(STATUS_LED_PIN, HIGH);
  }
}

Debugging: First Three Things to Check When It Fails

Embedded development is mostly debugging. If your serial monitor halts or throws an error, follow this ranked decision tree based on the exact error strings generated by the code above.

1. Exact Error: MQTT connection failed! Error code = -2

The ArduinoMqttClient library throws a -2 when the TCP socket connects, but the MQTT broker rejects the handshake.

  • Cause A (Most Likely): Client ID collision. The public HiveMQ broker drops connections if two devices use the same ID. Fix: Change mqttClient.setId("NanoESP32_Lab_01"); to a unique string like "NanoESP32_Lab_02".
  • Cause B: Port mismatch. You are trying to connect to an SSL/TLS broker on port 8883 using a plaintext WiFiClient. Fix: Ensure mqtt_port is 1883 for plaintext, or switch to WiFiClientSecure for TLS.
  • Cause C: Broker offline. Fix: Ping broker.hivemq.com from your PC to verify the public test server hasn't gone down for maintenance.

2. Exact Error: FATAL: BME280 init failed! Check I2C wiring and address.

The Wire library cannot find a device acknowledging at address 0x77.

  • Cause A (Most Likely): I2C address mismatch. Some cheap clone BME280 boards use 0x76 instead of 0x77. Fix: Run an I2C scanner sketch. If it reports 0x76, change the code to bme.begin(0x76, &Wire);.
  • Cause B: Missing pull-up resistors. If you are using a raw BME280 chip on a bare PCB instead of the Adafruit breakout, the I2C bus will float. Fix: Solder 10kΩ resistors between SDA/3V3 and SCL/3V3.
  • Cause C: Powering the sensor with 5V, which tripped its internal LDO and fried the chip. Fix: Replace the sensor and verify 3.3V at the source.

3. Exact Error: FATAL: WiFi connection failed. Check credentials.

The ESP32-S3 radio initialized, but the router rejected the association request.

  • Cause A (Most Likely): 5GHz vs 2.4GHz band. The ESP32-S3 only supports 2.4GHz WiFi. If your router uses a unified SSID for both bands and pushes the ESP32 to 5GHz, it will fail. Fix: Create a dedicated 2.4GHz IoT SSID on your router.
  • Cause B: WPA3 Enterprise security. The standard WiFi.begin() struggles with enterprise certificates. Fix: Use WPA2-Personal (PSK) for IoT nodes.

How to Extend or Simplify the Build

Once the baseline MQTT logger is stable, you need to decide how to adapt it for your specific environment. Do not leave the project in a "it works on my desk" state. Choose one of these two concrete paths:

Path A: Simplify for Offline / Remote Deployment

If you are deploying this in a greenhouse or shed without WiFi, strip the MQTT and WiFi libraries entirely to save flash memory and power. The concrete action: Add the Adafruit MicroSD Card Breakout (PID 254). Wire it to the Nano ESP32's hardware SPI pins (MOSI=D11, MISO=D12, SCK=D13, CS=D10). Replace the mqttClient.print() calls with File dataFile = SD.open("log.csv", FILE_WRITE);. This turns the node into a rugged, offline black-box logger that requires zero network infrastructure.

Path B: Extend for Battery Power and Deep Sleep

Running an ESP32 continuously on WiFi drains about 80mA, which will kill a standard 9V battery in hours. To run this node for 6 months on a single 18650 Li-ion cell, you must use the ESP32's Ultra-Low Power (ULP) co-processor and deep sleep modes. The concrete action: Add a esp_sleep_enable_timer_wakeup(600 * 1000000ULL); command at the end of your loop(), followed by esp_deep_sleep_start();. This shuts down the CPU and WiFi radio, waking the board only every 10 minutes to take a reading, publish via MQTT, and go back to sleep, dropping average current draw to under 15µA.

Understanding what you can do with Arduino means moving past the blink sketch and leveraging the specific silicon capabilities of modern boards. By matching the Nano ESP32 to an I2C sensor and MQTT protocol, you bridge the gap between hobbyist breadboarding and industrial IoT architecture.