When makers search for practical Arduino uses, they often move past blinking LEDs and basic motor control into real-world data acquisition. Environmental monitoring—tracking temperature, humidity, and barometric pressure—is one of the most reliable and useful applications for embedded systems. However, chaining multiple sensors together introduces I2C bus complexities, logic-level mismatches, and address conflicts that can stall a build.
This guide cuts through the theory and provides a decision-forward framework for selecting the right microcontroller, followed by a complete build for an I2C multi-sensor hub. We will use the Arduino Nano 33 IoT to poll a Bosch BME280 and a Sensirion SHT31-D simultaneously, complete with robust error handling and debugging paths.
The Decision Tree: Matching Arduino Uses to the Right Board Variant
Not every project needs the same silicon. Choosing the wrong board leads to logic-level frying or running out of flash memory. Use this decision matrix to match your specific use case to the correct hardware.
| If your project requires... | Then choose this board... | Because... |
|---|---|---|
| High pin count & 5V logic tolerance | Arduino Mega 2560 | 54 I/O pins, 5V native logic, legacy shield support. |
| Cloud IoT, WiFi/BLE & low power | Arduino Nano 33 IoT | Built-in NINA-W10 WiFi/BLE, 3.3V logic, compact footprint. |
| High-speed DSP, vision & dual-core | Arduino Portenta H7 | Dual-core Cortex-M7/M4, high-speed ADCs, industrial grade. |
| Basic 5V logic & simple prototyping | Arduino Uno R4 Minima | Renesas RA4M1 32-bit ARM, 5V tolerant, standard shield layout. |
Project Build: I2C Multi-Sensor Environmental Hub
This build polls two high-accuracy environmental sensors over a shared I2C bus. The BME280 provides barometric pressure and altitude, while the SHT31-D provides high-precision relative humidity and temperature. By comparing the temperature readings from both, you can cross-verify sensor health and detect localized thermal anomalies.
Parts List & Specifications
- Microcontroller: Arduino Nano 33 IoT (ABX00027) - ~$22.00
- Pressure/Temp Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652) - ~$20.00
- Humidity/Temp Sensor: Adafruit SHT31-D I2C Breakout (Product ID: 2857) - ~$14.00
- Prototyping: Half-size solderless breadboard, 22 AWG solid-core jumper wires.
Pin Mapping Table
The Nano 33 IoT operates strictly at 3.3V. Both Adafruit breakouts feature onboard voltage regulators and logic level shifters, making them safe to wire directly to the Nano's 3.3V pins. For bare OEM modules without shifters, you would need a logic level converter like the BSS138.
| Nano 33 IoT Pin | Function | BME280 Breakout | SHT31-D Breakout |
|---|---|---|---|
| 3V3 | Power (3.3V) | VIN / 3Vo | VIN |
| GND | Ground | GND | GND |
| A4 | I2C SDA | SDA | SDA |
| A5 | I2C SCL | SCL | SCL |
Step-by-Step Wiring and Assembly
- Seat the Microcontroller: Press the Arduino Nano 33 IoT into the center trench of the half-size breadboard, ensuring the USB port faces the edge for cable clearance.
- Establish Power Rails: Connect the Nano's
3V3pin to the red power rail and theGNDpin to the blue ground rail. Do not use the 5V/VUSB pin for these specific sensor breakouts to avoid back-feeding current. - Wire the I2C Data Lines: Run 22 AWG jumper wires from Nano pin
A4(SDA) to the SDA pins on both breakouts. Run another wire from Nano pinA5(SCL) to the SCL pins on both breakouts. - Distribute Power: Connect the red power rail to the
VINpins on both the BME280 and SHT31-D. Connect the blue ground rail to theGNDpins on both sensors. - Verify Address Pads: Check the back of the BME280 breakout. Ensure the I2C address jumper pad is set to the default
0x77. The SHT31-D defaults to0x44. Because these addresses differ, no bus conflict will occur.
Complete Firmware with I2C Error Handling
The following C++ code targets the Arduino Nano 33 IoT. It utilizes the Wire library alongside Adafruit's unified sensor drivers. Unlike basic tutorials that assume perfect wiring, this firmware includes explicit initialization checks and non-blocking polling via millis().
Required Libraries (install via Arduino Library Manager): Adafruit BME280 Library, Adafruit SHT31 Library, and Adafruit Unified Sensor.
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_SHT31.h>
// Pin definitions specific to Arduino Nano 33 IoT
#define I2C_SDA_PIN A4
#define I2C_SCL_PIN A5
// Sensor objects
Adafruit_BME280 bme;
Adafruit_SHT31 sht = Adafruit_SHT31();
// Non-blocking timing variables
unsigned long lastRead = 0;
const unsigned long READ_INTERVAL = 2000; // Poll every 2 seconds
void setup() {
Serial.begin(115200);
// Wait for serial port to connect (native USB boards)
while (!Serial) delay(10);
Serial.println(F("Multi-Sensor Environmental Hub Booting..."));
// Initialize I2C bus on specific pins for Nano 33 IoT
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
// Initialize BME280 at default I2C address 0x77
if (!bme.begin(0x77)) {
Serial.println(F("ERROR: Could not find a valid BME280 sensor, check wiring or I2C address!"));
// Halt execution to prevent reading garbage data
while (1) delay(10);
}
Serial.println(F("BME280 initialized successfully."));
// Initialize SHT31 at default I2C address 0x44
if (!sht.begin(0x44)) {
Serial.println(F("ERROR: Couldn't find SHT31 - check I2C wiring and address!"));
while (1) delay(10);
}
Serial.println(F("SHT31 initialized successfully.\n"));
// Set BME280 oversampling for indoor environmental monitoring
bme.setSampling(Adafruit_BME280::MODE_NORMAL,
Adafruit_BME280::SAMPLING_X2, // Temp
Adafruit_BME280::SAMPLING_X16, // Pressure
Adafruit_BME280::SAMPLING_X1, // Humidity
Adafruit_BME280::FILTER_X16,
Adafruit_BME280::STANDBY_MS_500);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastRead >= READ_INTERVAL) {
lastRead = currentMillis;
// Read BME280 Data
float bme_temp = bme.readTemperature();
float bme_hum = bme.readHumidity();
float bme_pres = bme.readPressure() / 100.0F; // Convert Pa to hPa
// Read SHT31 Data
float sht_temp = sht.readTemperature();
float sht_hum = sht.readHumidity();
// Check for NaN (Not a Number) returns indicating I2C read failure
if (isnan(sht_temp) || isnan(sht_hum)) {
Serial.println(F("WARNING: SHT31 read failed. I2C bus may be locked up."));
} else {
Serial.print(F("BME280 -> Temp: ")); Serial.print(bme_temp); Serial.print(F(" C | Hum: ")); Serial.print(bme_hum); Serial.print(F(" % | Pres: ")); Serial.print(bme_pres); Serial.println(F(" hPa"));
Serial.print(F("SHT31 -> Temp: ")); Serial.print(sht_temp); Serial.print(F(" C | Hum: ")); Serial.print(sht_hum); Serial.println(F(" %"));
Serial.println(F("---------------------------------------------------"));
}
}
}
Debugging: "Sensor Not Found" and I2C Bus Lockups
I2C is a robust protocol on paper, but in practice, it is highly susceptible to physical layer issues. If your serial monitor outputs the exact error string: "Could not find a valid BME280 sensor, check wiring or I2C address!", do not immediately assume the sensor is dead. Follow this ranked troubleshooting path.
The First 3 Things to Check When It Fails
- Logic Level Mismatch: The Nano 33 IoT outputs 3.3V on SDA/SCL. If you are using bare OEM sensor modules (not Adafruit/SparkFun breakouts) that require 5V pull-ups, the 3.3V high signal won't cross the logic threshold. Fix: Add a bidirectional logic level shifter (like the Texas Instruments TXB0104) or switch to a 5V board like the Uno R4.
- Missing Pull-Up Resistors: The I2C specification requires pull-up resistors on both SDA and SCL lines (NXP I2C-bus specification). Adafruit breakouts include 10kΩ pull-ups onboard. If you wired raw chips, the bus will float and fail initialization. Fix: Solder 4.7kΩ resistors between VCC and SDA/SCL.
- I2C Address Collision: If you accidentally wired two BME280s instead of a BME280 and an SHT31, both will respond to
0x77, causing bus arbitration failure. Fix: Bridge the address jumper pad on one BME280 to change its address to0x76, and update thebme.begin(0x76)argument in the code.
Ranked Causes for Intermittent Bus Lockups (NaN Returns)
If the code boots but later throws the WARNING: SHT31 read failed message, the I2C bus has locked up. This happens when a sensor is interrupted mid-transmission, causing it to hold the SDA line low.
- Cause 1: Capacitive Load / Wire Length. Long jumper wires add capacitance to the I2C lines, rounding off the square wave edges. Keep I2C runs under 30cm (1 foot).
- Cause 2: Power Brownouts. The WiFi radio on the Nano 33 IoT draws peak current during transmission. If powered via a weak USB hub, the voltage dips, resetting the sensors mid-read. Power the Nano via a high-quality 5V/2A wall adapter.
- Cause 3: Missing Ground Bonding. If you are using external power supplies for the sensors, their ground must be bonded directly to the Nano's GND pin to maintain a common reference potential.
Extending and Simplifying the Build
Depending on your end goal, you may need to scale this hardware up or strip it down. Here is how to modify the build without rewriting the core architecture.
How to Simplify
If you only need basic room climate data and want to reduce the BOM cost by ~$14, drop the SHT31-D entirely. The BME280 alone provides highly accurate temperature and humidity. To simplify the code, remove the Adafruit_SHT31.h includes, delete the sht object initialization, and remove the SHT polling block from the loop(). If you don't need WiFi cloud logging and want 5V tolerance for legacy shields, swap the Nano 33 IoT for an Arduino Uno R4 Minima and change the Wire.begin() call to use the default SDA/SCL pins without arguments.
How to Extend
To make this a standalone unit without a PC attached, add a 0.96" SSD1306 I2C OLED display (Address 0x3C). Because 0x3C does not conflict with 0x77 or 0x44, it drops right onto the existing SDA/SCL rails. You will need to add the Adafruit_SSD1306 library and render the float variables using display.print(). For cloud extension, utilize the Nano 33 IoT's built-in NINA-W10 module by including the WiFiNINA and ArduinoMqttClient libraries to publish the JSON-formatted sensor payload to an MQTT broker like Mosquitto or AWS IoT Core every 60 seconds.
For more detailed hardware specifications, refer to the official Arduino Nano 33 IoT documentation and the Adafruit BME280 wiring guide.






