A bare-metal embedded Rust application for the NUCLEO-F446RE board that implements an Industrial Internet of Things (IIoT) environmental monitoring node. The system integrates a Bosch BME680 multi-sensor with an SSD1306 OLED display over a shared I2C bus to provide real-time environmental data and calculated Indoor Air Quality (IAQ) metrics.
| Component | Model/Part | Interface | Address/Pins |
|---|---|---|---|
| Microcontroller | STM32F401RE (NUCLEO-F446RE) | - | - |
| Environmental Sensor | Bosch BME680 | I2C | 0x77 (Secondary) |
| Display | SSD1306 128x32 OLED | I2C | 0x3C |
| Status LED | Built-in LED | GPIO | PA5 |
| I2C Bus | I2C1 | SCL/SDA | PB8/PB9 |
- Temperature: -40°C to +85°C (±1°C accuracy)
- Humidity: 0-100% RH (±3% accuracy)
- Pressure: 300-1100 hPa (±1 hPa accuracy)
- Gas Resistance: 0-500 kΩ (Metal Oxide VOC sensor)
stm32f4xx-hal = "0.23.0" # STM32F4 Hardware Abstraction Layer
bme680 = "0.6" # BME680 sensor driver
ssd1306 = "0.8" # SSD1306 OLED driver
shared-bus = "0.3" # Shared I2C bus manager
embedded-graphics = "0.8" # Graphics primitives
heapless = "0.8" # Stack-allocated data structures
cortex-m-rt = "0.7" # Cortex-M runtime
libm = "0.2" # Math functions for no_std- Language: Rust - [1.91.1]
- Build System: Cargo with embedded targets
- Target:
thumbv7em-none-eabihf - Debugger: probe-run / VS Code + Cortex-Debug
- Flash Tool: cargo-embed / ST-LINK
- Pattern:
shared-bus::BusManagerSimpleenables safe concurrent access to I2C1 - Benefit: Prevents bus conflicts between BME680 and SSD1306 without complex state machines
- Implementation: Each peripheral acquires the bus using
bus.acquire_i2c()for transactions
Trigger (ForcedMode) → Wait (2s) → Read → Process → Display → Sleep
- Power Mode:
PowerMode::ForcedModefor single-shot measurements - Advantage: Minimizes sensor power consumption vs. continuous mode
- IIoT Relevance: Suitable for battery-powered edge nodes with periodic reporting
The system implements a custom IAQ algorithm combining:
- Gas Score (75%): Ratio of baseline gas resistance to current reading
- High resistance = clean air, low resistance = VOC presence
- Humidity Score (25%): Deviation from optimal humidity (40% RH)
- Outside 40% affects perceived air quality and mold risk
IAQ Scale:
- 0-50: Excellent
- 51-100: Good
- 101-150: Fair
- 151-200: Poor
- 201-300: Bad
- 301-500: Worst
- Initial Baseline: 2 MΩ (conservative high value)
- Auto-Calibration: System continuously updates baseline to highest observed resistance
- Burn-in Period: 48 hours recommended for stable baseline convergence
- Real-world Behavior: Tracks environmental changes (seasons, ventilation patterns)
The 128x32 OLED displays three lines of real-time metrics, updated every 2 seconds:
Line 1: [Temp]°C | [Hum]% | [IAQ Status]
Line 2: [Pressure]hPa | IAQ: [Score]
Line 3: Gas: [Resistance]kΩ
| Line | Metric | Unit | Range | Description |
|---|---|---|---|---|
| 1 | Temperature | °C | -40 to +85 | Ambient temperature with ±1°C accuracy |
| 1 | Humidity | % RH | 0-100 | Relative humidity with ±3% accuracy |
| 1 | IAQ Status | Text | EXCELLENT to WORST | Qualitative air quality assessment |
| 2 | Pressure | hPa | 300-1100 | Barometric pressure (altitude compensated) |
| 2 | IAQ Score | 0-500 | 0-500 | Numerical air quality index |
| 3 | Gas Resistance | kΩ | 0-500+ | Raw VOC sensor resistance |
- LED Behavior:
- Rapid blink (100ms): Initialization error
- 3 quick blinks: System ready
- Brief pulse: Measurement in progress
# Install Rust nightly
rustup toolchain install nightly
rustup default nightly
# Add embedded target
rustup target add thumbv7em-none-eabihf
# Install cargo tools
cargo install probe-run cargo-embedcargo build --release# Using probe-run
cargo run --release
# Or using cargo-embed
cargo embed --release// System Clock: 84 MHz (max for STM32F401RE)
let rcc_config = Config::default().sysclk(84.MHz());SettingsBuilder::new()
.with_humidity_oversampling(OversamplingSetting::OS2x)
.with_pressure_oversampling(OversamplingSetting::OS4x)
.with_temperature_oversampling(OversamplingSetting::OS2x)
.with_temperature_filter(IIRFilterSize::Size3)
.with_gas_measurement(Duration::from_millis(150), 300, 25)
.with_run_gas(true)// 100 kHz for reliable communication with both peripherals
I2c::new(dp.I2C1, (scl, sda), 100.kHz(), &mut rcc)- Update Rate: 2 seconds per measurement cycle
- Power Consumption: ~40mA active (BME680 heater on), ~10mA idle
- I2C Speed: 100 kHz (standard mode)
- Flash Usage: ~35 KB
- RAM Usage: ~8 KB
- Measurement Accuracy: Sensor-limited (see BME680 datasheet)
- Verify I2C address (use I2C scanner if available)
- Check wiring: SCL→PB8, SDA→PB9, VCC→3.3V, GND→GND
- Ensure pull-up resistors on I2C lines (often on-board)
- Try reducing I2C speed to 50 kHz
- Humidity at 100%: Wait 48 hours for sensor burn-in
- Gas resistance at 0: Heater warming up, wait ~5 minutes
- IAQ shows "...Wait": Normal during initial measurements
- Ensure correct HAL version:
stm32f4xx-hal = "=0.23.0"(note the=) - Clear build cache:
cargo clean - Update dependencies:
cargo update
- UART data logging for long-term trend analysis
- LoRaWAN integration for remote monitoring
- Sleep mode implementation for battery operation
- SD card logging of historical data
- Web server with ESP8266/ESP32 co-processor
- MQTT publishing to cloud platforms
- Altitude compensation for pressure readings
- CO₂ equivalent calculation from TVOC
- Store gas baseline in EEPROM/Flash for persistence
- Implement exponential moving average for noise reduction
- Add temperature compensation for gas readings
- Multi-point humidity calibration
- BME680 Datasheet
- STM32F401RE Reference Manual
- stm32f4xx-hal Documentation
- Embedded Rust Book
- IAQ Calculation Methods (Bosch)
This project is open-source and available under the MIT License.
Contributions are welcome! Please feel free to submit issues or pull requests for:
- Bug fixes
- Documentation improvements
- New features
- Performance optimizations