When planning arduino and raspberry pi projects, makers often hit a wall trying to force a single board to do everything. The Raspberry Pi excels at high-level compute, local databases, and web dashboards, but it struggles with deterministic, microsecond-level hardware polling due to its Linux-based OS. The Arduino excels at real-time I/O and low-power sensor reading, but lacks the RAM and native networking stack to run a local Grafana dashboard or handle complex TLS encryption. The solution is a hybrid architecture: offloading real-time sensor acquisition to the microcontroller and routing the data via MQTT to the single-board computer for processing.
The Decision Matrix: When to Combine Arduino and Raspberry Pi
Before ordering parts, run your project requirements through this decision path. Do not default to a hybrid build if a single board suffices, but do not force a Pi to handle raw I2C polling if timing jitter will ruin your data.
| Criteria | Pi Only (e.g., Pi 5) | Arduino Only (e.g., Nano 33 IoT) | Hybrid (Pi + Arduino) |
|---|---|---|---|
| Real-time Sensor Polling | Poor (Linux scheduling jitter) | Excellent (Deterministic) | Excellent (Arduino handles I/O) |
| Local UI / Dashboard | Excellent (Runs Node-RED/Grafana) | Poor (Requires cloud routing) | Excellent (Pi hosts the UI) |
| Power Consumption | High (~5-8W idle) | Low (~50mW) | Medium (Pi always on, Arduino sleeps) |
| Cost (2026 Estimates) | ~$60 - $80 | ~$20 - $30 | ~$80 - $110 |
Hardware Spec Sheet and Pin Mapping
This build uses the Arduino Nano 33 IoT (ABX00020) because it includes the NINA-W102 Wi-Fi module natively, eliminating the need for bulky external ESP-01 shields. The Raspberry Pi 5 (4GB) serves as the broker.
Parts List
- Compute: Raspberry Pi 5 (4GB RAM) - ~$60
- Microcontroller: Arduino Nano 33 IoT (ABX00020) - ~$22
- Sensor: Adafruit BME280 I2C/SPI Breakout (PID 2652) - ~$15
- Wiring: 22 AWG silicone jumper wires, 4-pin JST-SH cable (optional)
- Power: Official 27W USB-C Pi 5 Power Supply (powers Pi; Pi USB powers Arduino)
Pin Mapping Table (Nano 33 IoT to BME280)
The Adafruit PID 2652 breakout includes an onboard 3.3V LDO regulator and I2C level shifters, meaning you can safely wire it to the Nano 33 IoT's 3.3V logic without external resistors.
| BME280 Breakout Pin | Arduino Nano 33 IoT Pin | Function / Notes |
|---|---|---|
| VIN | 3V3 | 3.3V Power (Do not use 5V out) |
| GND | GND | Common Ground |
| SCK / SCL | A5 (SCL) | I2C Clock Line |
| SDI / SDA | A4 (SDA) | I2C Data Line |
Step-by-Step Assembly and Wiring
- Prep the Pi: Flash Raspberry Pi OS (64-bit, Bookworm) using Raspberry Pi Imager. Enable SSH and configure your Wi-Fi in the imager settings. Boot the Pi, SSH in, and install Mosquitto:
sudo apt update && sudo apt install mosquitto mosquitto-clients. - Configure the Broker: Edit the Mosquitto config to allow local network connections. Run
sudo nano /etc/mosquitto/mosquitto.conf, addlistener 1883andallow_anonymous true, then restart the service:sudo systemctl restart mosquitto. - Wire the Sensor: Connect the BME280 to the Nano 33 IoT using the pin mapping table above. Double-check that SDA and SCL are not swapped; I2C will silently fail if reversed.
- Power the Node: Plug the Nano 33 IoT into one of the Pi 5's USB-C/USB-A ports. This provides clean 5V to the Nano's onboard regulator, which steps it down to 3.3V for the SAMD21 and NINA-W102 chips.
The Firmware: Compilable C++ with Error Handling
This code targets the Arduino Nano 33 IoT. It uses the WiFiNINA library for the NINA-W102 chip and ArduinoMqttClient for the protocol. It includes explicit error handling for sensor initialization, Wi-Fi drops, and MQTT timeouts.
#include <SPI.h>
#include <WiFiNINA.h>
#include <ArduinoMqttClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// --- Pin Definitions ---
#define BME_SDA A4
#define BME_SCL A5
// --- Network & MQTT Config ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_broker = "192.168.1.100"; // Replace with your Pi 5 IP
int mqtt_port = 1883;
// --- Object Instantiation ---
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
Adafruit_BME280 bme;
// --- Timing Variables ---
unsigned long lastMillis = 0;
const long interval = 5000; // Publish every 5 seconds
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
// 1. Initialize I2C and Sensor
Wire.begin(BME_SDA, BME_SCL);
if (!bme.begin(0x77)) { // Adafruit breakouts default to 0x77
Serial.println("FATAL: Could not find a valid BME280 sensor, check wiring or I2C address!");
while (1) { delay(100); } // Halt execution
}
// 2. Connect to Wi-Fi
Serial.print("Connecting to Wi-Fi...");
WiFi.begin(ssid, password);
int wifi_attempts = 0;
while (WiFi.status() != WL_CONNECTED && wifi_attempts < 20) {
delay(500);
Serial.print(".");
wifi_attempts++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nERROR: Wi-Fi connection failed. Check credentials.");
while(1) { delay(1000); }
}
Serial.println("\nConnected. IP: " + WiFi.localIP().toString());
// 3. Configure MQTT
mqttClient.setId("Nano33IoT_Node1");
mqttClient.setKeepAliveInterval(60000);
mqttClient.setConnectionTimeout(5000);
}
void loop() {
// Maintain MQTT connection
if (!mqttClient.connected()) {
Serial.print("Connecting to MQTT broker...");
if (!mqttClient.connect(mqtt_broker, mqtt_port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
delay(5000); // Wait 5s before retrying
return;
}
Serial.println("Connected to broker.");
}
// Poll MQTT to keep connection alive
mqttClient.poll();
// Publish data on interval
unsigned long currentMillis = millis();
if (currentMillis - lastMillis >= interval) {
lastMillis = currentMillis;
float temp = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
// Format JSON payload manually to avoid heavy ArduinoJson library overhead
String payload = "{\"temp\":" + String(temp, 2) +
",\"hum\":" + String(humidity, 1) +
",\"pres\":" + String(pressure, 2) + "}";
mqttClient.beginMessage("home/lab/environment");
mqttClient.print(payload);
int endResult = mqttClient.endMessage();
if (endResult == 0) {
Serial.println("Published: " + payload);
} else {
Serial.println("MQTT Publish failed with code: " + String(endResult));
}
}
}
Debugging: First Three Checks and Exact Error Strings
When hybrid projects fail, the fault usually lies at the boundary between hardware and network. If your Serial monitor halts or throws errors, follow this ranked diagnostic path.
- I2C Address & Pull-ups: Run the Adafruit I2C Scanner sketch. If it finds nothing, check SDA/SCL swap. If you are using a cheap clone BME280 (not the Adafruit PID 2652), it may lack onboard pull-up resistors, requiring external 4.7kΩ resistors to 3.3V.
- Broker IP & Firewall: Ping the Pi 5 from your main PC. Ensure UFW (Uncomplicated Firewall) on the Pi isn't blocking port 1883 (
sudo ufw allow 1883). - Wi-Fi Band: The NINA-W102 chip on the Nano 33 IoT is strictly 2.4GHz. If your router uses a unified SSID for 2.4GHz and 5GHz, the module may fail to associate. Force the router to broadcast a dedicated 2.4GHz SSID.
Exact Error Strings and Fixes
FATAL: Could not find a valid BME280 sensor, check wiring or I2C address!
Cause: Thebme.begin(0x77)call failed. Some generic breakouts use address0x76. Change the hex address in the code and re-flash.MQTT connection failed! Error code = -2
Cause: Network connect failed. The Arduino reached the router but cannot route to the Pi's IP. Verify themqtt_brokerIP string matches the Pi's static IP.MQTT connection failed! Error code = -3
Cause: Connection timeout. The Pi's Mosquitto service is likely crashed or not running. SSH into the Pi and runsudo systemctl status mosquitto.
Scaling the Build: Extend or Simplify
Once the baseline hybrid architecture is stable, you will inevitably need to adapt it to physical constraints. Here is how to modify the build without rewriting the core logic.
How to Simplify (Drop the Pi)
If you only need to view data remotely and do not care about local network privacy or offline availability, eliminate the Raspberry Pi entirely. Change the mqtt_broker IP to a free cloud tier broker like HiveMQ or Adafruit IO. This reduces hardware cost by $60 and drops the system power draw from ~8W to under 1W, making it viable for battery/solar operation.
How to Extend (Add ESP-NOW Mesh)
If you need to cover a large property (e.g., a multi-building farm or warehouse), Wi-Fi range will bottleneck the Nano 33 IoT. Swap the Nano 33 IoT for an ESP32-S3. The ESP32 supports ESP-NOW, allowing you to create a mesh of ultra-low-power sensor nodes that broadcast to a single ESP32 gateway over a kilometer away (line-of-sight). The gateway then pushes the aggregated JSON payloads to the Pi 5 via MQTT over Wi-Fi, preserving your existing dashboard infrastructure.






