Putting It All Together: BME280 Integration and Debugging
Putting it all together#
Today was spent bringing everything together.
The first task was to extract the BME280 sensor from a previous project to replace the BMP280 module I had received in error. With the correct sensor now in place, I moved on to building out the software to interrogate it.
The firmware now captures:
- Temperature
- Humidity
- Pressure
- Calculated altitude
These values are displayed on the OLED screen and also transmitted over UART as debug output to a connected PC.
At this point, the system feels like a complete device rather than just a collection of components.
BME280 sensor#
The sensor I originally ordered for this project turned out to be a BMP280, which I discovered during initial testing. While the BMP280 is still useful, it lacks humidity sensing, which is a requirement for this build.
Fortunately, I had a spare BME280 available from a previous project, so I carefully removed it and repurposed it for the Weather Station board.

BME280 sensor (notice the square casing)

BMP280 sensor (notice the rectangular casing)
At a glance, the two modules look almost identical, which makes this an easy mistake to miss—especially when ordering from marketplaces like AliExpress.
In my case, although the packaging indicated BME280, the modules were actually BMP280, something that only became obvious once I started writing code to read humidity values.
However, there are a couple of subtle physical differences:
- The BME280 sensor package is slightly larger and has a more square profile
- The BMP280 package is smaller and more rectangular
In practice, though, the most reliable way to distinguish between them is via software. The device ID register can be queried over I²C or SPI (returning 0x60 for the BME280 and 0x58 for the BMP280), allowing the firmware to positively identify the sensor type.
Why this matters#
This kind of mix-up is more than just a minor annoyance.
If you assume you have a BME280 and write firmware expecting humidity data, you can end up chasing non-existent bugs in your code when the real issue is simply incorrect hardware.
It’s a good reminder of a general rule in embedded development:
Always verify your hardware before debugging your software.
The Code#
I won’t detail the complete code in this iteration, as it still needs to go through a revision or two and was written quickly to validate the board. However, the current implementation is based on an Arduino sketch using the following libraries:
- Adafruit BME280 (and dependencies) — for interfacing with the BME280 sensor
- Adafruit SH110X (and dependencies) — for driving the 1.3" OLED display
- Wire — for I²C communication
The firmware periodically reads sensor data, updates the display, and outputs formatted debug information over UART for verification and troubleshooting.
As with any Arduino-based application, the structure consists of a setup() routine and a loop() routine.
Setting up the BME280 sensor#
The code for initialising the BME280 sensor is derived from the Adafruit bme280test example sketch.
The relevant section is shown below:
unsigned status;
// default settings
status = bme.begin();
// You can also pass in a Wire instance, e.g.
// status = bme.begin(0x76, &Wire2);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
Serial.print("SensorID was: 0x"); Serial.println(bme.sensorID(), HEX);
Serial.println("ID of 0xFF probably means a bad address, BMP180 or BMP085");
Serial.println("ID of 0x56-0x58 represents a BMP280");
Serial.println("ID of 0x60 represents a BME280");
Serial.println("ID of 0x61 represents a BME680");
while (1) delay(10);
}
// Always print detected sensor ID (even on success)
Serial.print("SensorID: 0x");
Serial.println(bme.sensorID(), HEX);
Serial.println("-- Default Test --");
delayTime = 1000;
If initialisation fails, the code reports the detected sensor ID over UART.
In my case, the sensor returned 0x58, confirming that it was a BMP280 rather than the expected BME280.
The current implementation assumes the sensor is available at address 0x77 on the I²C bus (though some modules default to 0x76, depending on how the address pin is configured).
Setting up the OLED display#
The next section of code initialises and writes text to the SH1106-based 1.3" OLED display. This is based on the SH1106_128x64_i2c_QTPY example provided by the Adafruit SH110X library.
#define I2C_ADDRESS 0x3C // Typical I2C address for these OLED modules
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Not used on most small OLED modules
Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setupOLED() {
Serial.println("Display Setup");
delay(250); // Allow display to power up
display.begin(I2C_ADDRESS, true); // Initialise display
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0);
display.println("Protoworks 2026");
display.println("Weather Station");
display.println("Test Board");
display.display(); // Push buffer to screen
delay(2000);
}
This code initialises the display using its I²C address and prepares the internal frame buffer.
Text is written to the buffer first, and only displayed on the screen when display.display() is called. This buffered approach avoids flickering and provides full control over when updates are pushed to the display.
A short delay is included at the end to keep the startup message visible before normal operation begins.
Obtaining data from the BME280 Sensor#
Now that the UART, BME280 sensor, and OLED display have been initialised, the main loop can be executed.
//delay time is set to 1000ms
void loop() {
printValues();
delay(delayTime);
}
void printValues() {
double temp = bme.readTemperature();
double pressure = bme.readPressure();
double altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
double humidity = bme.readHumidity();
Serial.print("Temperature = ");
Serial.print(temp);
Serial.println(" °C");
Serial.print("Pressure = ");
Serial.print(pressure / 100.0F);
Serial.println(" hPa");
Serial.print("Approx. Altitude = ");
Serial.print(altitude);
Serial.println(" m");
Serial.print("Humidity = ");
Serial.print(humidity);
Serial.println(" %");
Serial.println();
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0);
display.print("Temp = ");
display.print(temp);
display.println(" Deg C");
display.print("Pres = ");
display.print(pressure / 100.0F);
display.println(" hPa");
display.print("Alti = ");
display.print(altitude);
display.println(" m");
display.print("Humi = ");
display.print(humidity);
display.println(" %");
display.display();
}
The firmware now enters a simple loop that reads data from the BME280 sensor, outputs the values over UART, and updates the display.
With regard to the altitude reading, SEALEVELPRESSURE_HPA should be set to the current local barometric pressure. It is typically initialised to a default of 1013.25 hPa, but in my area the correct value is usually between 1016 and 1023 hPa.
The result#
The video below shows the system running, with live sensor data displayed on the OLED screen.
This completes the initial bring-up of the Weather Station prototype, with further refinements planned for future iterations.
Next Steps#
This initial prototype was undertaken to validate my workflows and re-evaluate some assumptions from previous designs, while also highlighting new considerations when moving from prototyping to production.
Looking ahead to a future revision of the board, there are a number of areas I would revisit.
Think about testing and production earlier in the design process.
Early consideration of test points, programming access, and validation workflows can significantly reduce friction during bring-up and later iterations.Use reliable component sourcing for production.
While online marketplaces such as AliExpress are suitable for prototyping, they introduce uncertainty around part authenticity and availability. For production, components should be sourced from reputable distributors to ensure consistency and traceability.Optimise component sizes for production.
Moving from larger prototype-friendly components (e.g. 0805/1206) to smaller packages (e.g. 0603 or 0402) can reduce board size and cost. However, this requires careful consideration of power ratings, thermal characteristics, and assembly constraints.Re-evaluate component choices as newer parts become available.
Familiarity with existing components can be useful, but newer parts often offer improved efficiency, availability, and integration. Periodically reviewing component choices helps avoid locking designs to outdated parts.Maintain a reusable design library.
Keeping a library of proven circuits and design blocks allows for faster development and reduces risk when starting new projects.Standardise on USB-C for power where possible.
USB-C has become the de facto standard for power delivery and should be used unless there is a specific requirement that necessitates an alternative connector.Verify connectors and power paths before applying power.
Always check that connector pins are not shorted after assembly, particularly before any protection circuitry. Catching these issues early can prevent unnecessary damage during bring-up.Avoid pin headers in production designs where possible.
While pin headers are convenient for prototyping, they are not ideal for production. Consider using keyed or polarised connectors—especially for power connections—to improve reliability and prevent incorrect connections.
In this design, the use of pin headers made assembly flexible, but also highlighted how easily connections could be misaligned or loosely fitted.Consider reverse current paths in the design.
Components such as LDO regulators can allow current to flow in unexpected directions under certain conditions. In this design, reverse current was observed but only powered an indicator LED. In other scenarios, this could lead to unintended behaviour or damage.Include test points as part of the design.
Adding test points for key signals and power rails makes validation and debugging significantly easier during bring-up and future revisions.Use RF-certified modules where possible.
When incorporating wireless connectivity, using pre-certified modules can significantly reduce regulatory overhead and risk. For example, while the ESP32-C3-SuperMini used in this prototype is not certified, alternatives such as the ESP32-C3-WROOM-02-N4 offer certification and may be more suitable for production designs.Use 3D visualisation tools during PCB design.
Leveraging the 3D viewer in tools such as KiCad helps identify mechanical issues early, verify component placement, and ensure footprint correctness—particularly when working with components that provide associated 3D models.
Many of these points were reinforced by issues encountered during this prototype.
If you are working on custom carrier boards, sensor integration, or ESP32 prototypes and need an experienced engineering partner, let’s discuss your project at Protoworks (NI).
- 2026-10-17 Refining the Protoworks service offerings Lab
- 2026-09-27 Belfast Tech Week Lab
- 2026-09-19 Refining the UCMOD Architecture R&D
- 2026-09-06 Giving AI Agents a Way to Talk to Protoworks Software
- 2026-09-03 A Morning at a Manufacturing Trade Show Lab
- 2026-09-01 Debugging 921600 Baud on the ATtiny1614 R&D