The Core Dilemma: Raspberry Pi or Arduino for Sensor Nodes?
When planning a new environmental logging or IoT telemetry project, the 'pi or arduino' debate is the first hurdle every maker faces. Both ecosystems are powerful, but they solve fundamentally different engineering problems. A Raspberry Pi is a fully-fledged Linux computer optimized for high-level processing, local databases, and complex networking. An Arduino (specifically the modern ESP32-based variants) is a real-time microcontroller optimized for low-level hardware control, deterministic timing, and ultra-low power consumption.
If you are building a remote temperature/humidity sensor node that needs to run on battery power and publish data via MQTT, the decision path is straightforward. Use the decision matrix below to lock in your hardware choice.
Hardware Decision Matrix
| Project Requirement | Raspberry Pi 5 (4GB) | Arduino Nano ESP32 | Winner |
|---|---|---|---|
| Local ML inference or heavy web scraping | Excellent (Quad-core 2.4GHz) | Poor (Dual-core 240MHz) | Pi 5 |
| Battery operation & Deep Sleep current | Poor (Requires UPS HAT, idle ~2W) | Excellent (Native deep sleep ~10µA) | Nano ESP32 |
| Real-time deterministic I2C/SPI polling | Poor (Linux kernel interrupts) | Excellent (Bare-metal/FreeRTOS) | Nano ESP32 |
| Total System Cost (Board + Power + Storage) | ~$105 (Board + SD + 5A PSU) | ~$35 (Board + 18650 cell) | Nano ESP32 |
| DEFAULT PICK FOR SENSOR NODES | Overkill and power-hungry | Purpose-built for the task | Arduino Nano ESP32 |
For the remainder of this guide, we will execute the default recommendation: building a robust, low-power MQTT environmental sensor using the Arduino Nano ESP32. If your project requires a local dashboard or camera integration, pivot to the Pi 5, but for 90% of remote telemetry tasks, the microcontroller is the correct tool.
Hardware Showdown: Spec Sheet and Real-World Costs
Before we wire the board, it is critical to understand the hidden costs and physical constraints of the hardware. The sticker price of the board is only a fraction of the deployed system cost.
Board Specifications (2026 Market Pricing)
| Feature | Raspberry Pi 5 (4GB Model) | Arduino Nano ESP32 (ABX00092) |
|---|---|---|
| Processor | Broadcom BCM2712, Quad-core Cortex-A76 @ 2.4GHz | Espressif ESP32-S3, Dual-core LX7 @ 240MHz |
| RAM | 4GB LPDDR4X | 512KB SRAM + 8MB PSRAM (on some modules) |
| Wireless | Wi-Fi 5 (802.11ac), Bluetooth 5.0 | Wi-Fi 4 (802.11n), Bluetooth 5.0 + BLE |
| Operating System | Raspberry Pi OS (Debian Linux) | FreeRTOS / Bare-metal (Arduino Core) |
| Boot Time | 15 - 30 seconds | < 500 milliseconds |
| Active Power Draw | ~3W to 8W (requires active cooling) | ~80mA to 120mA (during Wi-Fi TX) |
| Deep Sleep Power | N/A (Requires external power gating) | ~10µA (native RTC sleep) |
| Approx. Board Price | $60.00 | $21.00 |
As documented in the official Arduino Nano ESP32 hardware guide, the board integrates the ESP32-S3 chip, which natively supports Wi-Fi and BLE without requiring external antennas. Conversely, the Raspberry Pi 5 requires a mandatory 27W USB-C PD power supply to prevent brownouts on the PCIe and USB buses, instantly adding $12 to your BOM (Bill of Materials).
The Winning Build: BME280 MQTT Logger
We are building an environmental node that reads temperature, humidity, and barometric pressure, then publishes it to an MQTT broker. This code specifically targets the Arduino Nano ESP32 (ABX00092) using the Arduino IDE with the 'Arduino ESP32 Boards' core installed.
Parts List
- Microcontroller: Arduino Nano ESP32 (Part: ABX00092)
- Sensor: Adafruit BME280 I2C/SPI Breakout (Part: 2652)
- Resistors: 2x 4.7kΩ through-hole resistors (for I2C pull-ups)
- Power: 18650 Li-Ion cell (e.g., Samsung 30Q) + 2-pin battery holder
- Wiring: 22 AWG solid core jumper wires
Bench Tip: While the Adafruit BME280 breakout includes 10kΩ onboard pull-up resistors, the ESP32-S3's I2C peripheral is notoriously sensitive to slow rise times on longer wire runs. Adding external 4.7kΩ pull-ups to the 3.3V rail ensures clean square waves and prevents intermittent timeout errors.
Pin Mapping Table
| BME280 Breakout Pin | Arduino Nano ESP32 Pin | Notes |
|---|---|---|
| VIN | 3V3 | Do NOT use 5V; the BME280 is a 3.3V device. |
| GND | GND | Common ground required. |
| SCK / SCL | A5 (SCL) | I2C Clock. Add 4.7kΩ pull-up to 3V3. |
| SDI / SDA | A4 (SDA) | I2C Data. Add 4.7kΩ pull-up to 3V3. |
| CSB | Not Connected | Left floating to set I2C address to 0x77. |
| SDO | Not Connected | Not used in I2C mode. |
Complete Compilable Code
Install the Adafruit BME280 Library and PubSubClient via the Arduino Library Manager before compiling. Ensure your board manager is set to the Arduino Nano ESP32.
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <PubSubClient.h>
// --- Pin Definitions ---
#define I2C_SDA_PIN A4
#define I2C_SCL_PIN A5
#define STATUS_LED_PIN 48 // Built-in RGB LED Red channel on Nano ESP32
// --- Network & MQTT Config ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;
const char* mqtt_topic_temp = "sensor/node1/temperature";
const char* mqtt_topic_hum = "sensor/node1/humidity";
// --- Object Instantiation ---
Adafruit_BME280 bme;
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
delay(10);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
attempts++;
}
if (WiFi.status() != WL_CONNECTED) {
ESP.restart(); // Hard reset if WiFi fails to prevent hanging
}
}
void reconnect_mqtt() {
while (!client.connected()) {
String clientId = "NanoESP32-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
// Connected
} else {
delay(5000); // Wait 5 seconds before retrying
}
}
}
void setup() {
Serial.begin(115200);
pinMode(STATUS_LED_PIN, OUTPUT);
digitalWrite(STATUS_LED_PIN, HIGH); // LED ON during setup
// Initialize I2C with explicit pins for ESP32
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
// Initialize BME280 with error handling
if (!bme.begin(0x77, &Wire)) {
Serial.println("Could not find a valid BME280 sensor, check wiring or use a different I2C address!");
while (1) {
// Blink LED rapidly to indicate fatal hardware fault
digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
delay(100);
}
}
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
digitalWrite(STATUS_LED_PIN, LOW); // LED OFF when ready
}
void loop() {
if (!client.connected()) {
reconnect_mqtt();
}
client.loop();
// Read sensors
float tempC = bme.readTemperature();
float humidity = bme.readHumidity();
// Publish data
client.publish(mqtt_topic_temp, String(tempC).c_str(), true);
client.publish(mqtt_topic_hum, String(humidity).c_str(), true);
Serial.printf("Published: %.2f C, %.2f %%\n", tempC, humidity);
// Deep sleep is omitted here for serial debugging, see extension section
delay(60000); // Wait 60 seconds before next reading
}
Debugging the Build: First Three Checks & Exact Error Strings
Embedded development rarely works perfectly on the first compile. When your serial monitor throws an error, follow this ranked troubleshooting path. Do not start rewriting code until you have verified the physical layer.
The Fatal I2C Error
If your serial monitor outputs the exact string:
Could not find a valid BME280 sensor, check wiring or use a different I2C address!
This means the ESP32 sent an I2C clock signal but received no ACK (acknowledge) bit back from the sensor. Here are the first three things to check, ranked by likelihood:
- Missing or Weak Pull-Up Resistors: The ESP32-S3 I2C pins do not have strong internal pull-ups enabled by default in the Arduino Wire library. If you omitted the external 4.7kΩ resistors on SDA and SCL, the signal lines will float, resulting in garbage data. Fix: Solder 4.7kΩ resistors between the SDA/SCL lines and the 3.3V rail.
- Logic Level Mismatch (Powering with 5V): If you accidentally wired the BME280 VIN pin to the 5V pin on the Nano ESP32, the sensor will output 5V logic on the SDA line. The ESP32-S3 GPIO pins are strictly 3.3V tolerant. Feeding 5V into A4 can permanently damage the GPIO pad or cause the I2C peripheral to lock up. Fix: Verify VIN is connected to 3V3. If you used 5V, replace the ESP32 board.
- I2C Address Conflict (0x76 vs 0x77): The Adafruit breakout defaults to 0x77. However, cheaper clone boards from Amazon/AliExpress often have the CSB pin pulled high, shifting the address to 0x76. Fix: Run an I2C scanner sketch to find the actual address, and change
bme.begin(0x77)tobme.begin(0x76)in the code.
MQTT Connection Drops
If the sensor reads fine but the MQTT broker shows intermittent disconnects, check your Wi-Fi router's DHCP lease time and ensure the client.loop() function is being called frequently enough in your main loop to process background keep-alive packets.
Extending and Simplifying the Node
Once the baseline build is stable, you will likely want to adapt it for your specific deployment environment. Here is how to modify the architecture without breaking the core logic.
Extending: Adding True Deep Sleep for Battery Life
The current code uses delay(60000), which keeps the ESP32-S3 CPU and Wi-Fi radio active, drawing ~80mA continuously. An 18650 cell (3000mAh) will die in less than two days. To extend battery life to several months, replace the delay() at the end of the loop() function with the Espressif deep sleep API. As detailed in the Espressif Sleep Modes documentation, this powers down the CPU and RAM, retaining only the RTC memory.
// Add to the very end of the loop() function:
const uint64_t SLEEP_DURATION_US = 15 * 60 * 1000000ULL; // 15 minutes
esp_sleep_enable_timer_wakeup(SLEEP_DURATION_US);
Serial.println("Going to sleep...");
Serial.flush();
esp_deep_sleep_start();
Note: When using deep sleep, the setup() function runs on every wake cycle. Ensure your MQTT connection logic can handle rapid reconnects, and consider using MQTT retained messages so the broker holds the last known value while the node sleeps.
Simplifying: Stripping the Network Stack
If you are deploying this in an off-grid cabin or a Faraday cage environment where Wi-Fi is unavailable, strip out the WiFi.h and PubSubClient.h dependencies entirely. Rely solely on the hardware serial port. Connect a 3.3V USB-to-Serial adapter (like an FTDI FT232RL) to the TX0 and RX0 pins, set your baud rate to 115200, and log the data directly to a local text file on a laptop. This reduces the compiled binary size by roughly 40% and eliminates all network-induced latency and failure modes.






