Back to Embedded Systems Hardware Engineering Series

Capstone 1: Smart Environmental Monitor

April 17, 2026 Wasil Zafar 50 min read

Build a complete IoT environmental sensor node — from requirements and schematic capture through PCB layout, firmware, enclosure, and field deployment.

Table of Contents

  1. System Requirements
  2. Architecture & BOM
  3. Schematic Highlights
  4. Solar Power Budget
  5. Firmware Overview
  6. BOM Calculator
  7. Conclusion

System Requirements

ParameterRequirementTarget
Temperature range−40°C to +85°CBME280 (±1°C)
Humidity0–100% RHBME280 (±3% RH)
Air qualityeCO2 + TVOCCCS811 (400–8192 ppm)
Particulate matterPM2.5 / PM10PMS5003 laser sensor
Connectivity>2 km range, low powerLoRa (SX1276, 868/915 MHz)
PowerSolar + battery, >7 days autonomy6V/2W panel + 3.7V 3400mAh Li-Ion
EnclosureOutdoor, IP65ABS with Stevenson screen
Size<120 × 80 × 50 mm (PCB)4-layer, 100 × 70 mm

System Architecture & BOM

Environmental Monitor Block Diagram
flowchart TD
    A["Solar Panel
6V / 2W"] --> B["MPPT Charger
BQ25504"] B --> C["Li-Ion Battery
3.7V 3400mAh"] C --> D["LDO 3.3V
TPS7A20"] D --> E["STM32L4
MCU"] E -->|I2C| F["BME280
Temp/Hum/Pres"] E -->|I2C| G["CCS811
Air Quality"] E -->|UART| H["PMS5003
Particulate"] E -->|SPI| I["SX1276
LoRa Radio"] I --> J["Antenna
868/915 MHz"] E -->|I2C| K["EEPROM
Data Log"] style E fill:#3B9797,color:#fff style A fill:#f5a623,color:#fff
# Bill of Materials — cost analysis for environmental monitor
# Prices in USD at qty 100

bom = [
    ("STM32L476RG",    "MCU, ARM Cortex-M4, 1MB Flash",   3.85, 1),
    ("BME280",         "Temp / Humidity / Pressure",       2.50, 1),
    ("CCS811",         "eCO2 / TVOC gas sensor",           5.80, 1),
    ("PMS5003",        "Laser particulate sensor",         12.50, 1),
    ("SX1276",         "LoRa transceiver 868/915MHz",      3.20, 1),
    ("BQ25504",        "MPPT solar charger IC",            3.40, 1),
    ("TPS7A20",        "LDO 3.3V 250mA ultra-low Iq",     0.65, 1),
    ("3400mAh Li-Ion", "18650 battery cell",               2.80, 1),
    ("6V 2W Solar",    "Monocrystalline panel",            4.50, 1),
    ("SMA Antenna",    "868/915MHz whip, 3dBi",            1.20, 1),
    ("PCB 4-layer",    "100x70mm, ENIG, 1.6mm",           2.40, 1),
    ("ABS Enclosure",  "IP65, 158x90x60mm",               3.50, 1),
    ("Passives kit",   "Caps, resistors, inductors",       1.20, 1),
    ("Connectors",     "JST-PH, SMA, headers",            0.80, 1),
]

print("Environmental Monitor BOM — Qty 100")
print("=" * 65)
print(f"{'Component':<20} {'Description':<35} {'Unit $':>7}")
print("-" * 65)

total = 0
for name, desc, price, qty in bom:
    line_total = price * qty
    total += line_total
    print(f"{name:<20} {desc:<35} ${price:>6.2f}")

print("-" * 65)
print(f"{'TOTAL BOM COST':<55} ${total:>6.2f}")
print(f"\nWith 15% assembly + overhead:  ${total * 1.15:.2f}")
print(f"Suggested retail (3x markup):  ${total * 3:.2f}")

Schematic Highlights

Key Design Decisions: The STM32L4 was chosen for ultra-low-power Stop 2 mode (1.1µA). The BQ25504 harvests energy from the solar panel down to 100mV cold-start. All sensors use I2C/UART to minimize pin count, and the SX1276 connects via SPI for maximum throughput.
/* Sensor initialization — I2C bus scan and verify
   All sensors on single I2C1 bus (PB6=SCL, PB7=SDA) */

#include <stdint.h>
#include <stdio.h>

/* I2C addresses (7-bit) */
#define BME280_ADDR    0x76  /* SDO=GND: 0x76, SDO=VDD: 0x77 */
#define CCS811_ADDR    0x5A  /* ADDR=LOW: 0x5A, ADDR=HIGH: 0x5B */
#define EEPROM_ADDR    0x50  /* A0=A1=A2=GND */

typedef struct {
    const char *name;
    uint8_t     addr;
    uint8_t     id_reg;
    uint8_t     expected_id;
} sensor_info_t;

sensor_info_t sensors[] = {
    {"BME280",  BME280_ADDR,  0xD0, 0x60},  /* Chip ID register */
    {"CCS811",  CCS811_ADDR,  0x20, 0x81},  /* HW_ID register */
    {"EEPROM",  EEPROM_ADDR,  0x00, 0x00},  /* No ID — ACK check only */
};

/* Power budget for sleep/wake cycle */
/*
 * Mode          | Current  | Duration | Energy (mAh)
 * --------------|----------|----------|-------------
 * Deep sleep    | 2.1 µA   | 595 s    | 0.00035
 * Sensor warmup | 25 mA    | 3 s      | 0.02083
 * Measurement   | 15 mA    | 1 s      | 0.00417
 * LoRa TX       | 120 mA   | 1 s      | 0.03333
 * Total/cycle   | —        | 600 s    | 0.05868
 * Per day (144) | —        | —        | 8.45 mAh
 * Battery life  | —        | —        | 402 days
 */

Solar Power Budget

# Solar power budget — daily energy balance
# Determines battery autonomy and solar panel sizing

# Battery
battery_capacity_mah = 3400   # 18650 cell
battery_voltage = 3.7         # Nominal voltage
battery_energy_wh = battery_capacity_mah * battery_voltage / 1000

# Power consumption per cycle (10-minute interval)
sleep_current_ua = 2.1
sleep_duration_s = 595
warmup_current_ma = 25
warmup_duration_s = 3
measure_current_ma = 15
measure_duration_s = 1
lora_current_ma = 120
lora_duration_s = 1

# Energy per cycle (mAh)
cycle_energy_mah = (
    sleep_current_ua / 1000 * sleep_duration_s / 3600 +
    warmup_current_ma * warmup_duration_s / 3600 +
    measure_current_ma * measure_duration_s / 3600 +
    lora_current_ma * lora_duration_s / 3600
)

cycles_per_day = 24 * 60 / 10  # every 10 minutes
daily_consumption_mah = cycle_energy_mah * cycles_per_day

# Solar harvest (conservative: 4 peak sun hours)
solar_panel_w = 2.0
mppt_efficiency = 0.80
peak_sun_hours = 4
daily_harvest_wh = solar_panel_w * mppt_efficiency * peak_sun_hours
daily_harvest_mah = daily_harvest_wh / battery_voltage * 1000

# Results
print("Solar Power Budget — Environmental Monitor")
print("=" * 55)
print(f"\nBattery: {battery_capacity_mah} mAh @ {battery_voltage}V = {battery_energy_wh:.1f} Wh")
print(f"\nPer cycle ({int(sleep_duration_s+warmup_duration_s+measure_duration_s+lora_duration_s)}s):")
print(f"  Sleep:      {sleep_current_ua} µA × {sleep_duration_s}s")
print(f"  Warmup:     {warmup_current_ma} mA × {warmup_duration_s}s")
print(f"  Measure:    {measure_current_ma} mA × {measure_duration_s}s")
print(f"  LoRa TX:    {lora_current_ma} mA × {lora_duration_s}s")
print(f"  Cycle total: {cycle_energy_mah:.5f} mAh")
print(f"\nDaily ({int(cycles_per_day)} cycles): {daily_consumption_mah:.2f} mAh")
print(f"\nSolar harvest: {solar_panel_w}W × {mppt_efficiency:.0%} eff × {peak_sun_hours}h")
print(f"  = {daily_harvest_wh:.1f} Wh/day = {daily_harvest_mah:.0f} mAh/day")
print(f"\nEnergy balance: +{daily_harvest_mah - daily_consumption_mah:.0f} mAh/day (surplus)")
battery_only_days = battery_capacity_mah / daily_consumption_mah
print(f"Battery-only autonomy: {battery_only_days:.0f} days")

Firmware State Machine

Sensor Node Firmware State Machine
stateDiagram-v2
    [*] --> INIT
    INIT --> SLEEP : Config OK
    SLEEP --> WAKE : RTC alarm
    WAKE --> MEASURE : Sensors ready
    MEASURE --> TRANSMIT : Data valid
    MEASURE --> LOG_ERROR : Sensor fail
    TRANSMIT --> SLEEP : TX complete
    LOG_ERROR --> SLEEP : Error stored
    TRANSMIT --> RETRY : TX fail
    RETRY --> TRANSMIT : Attempt < 3
    RETRY --> SLEEP : Max retries
                            

BOM Cost Calculator

Environmental Monitor BOM Calculator

Estimate BOM cost at different production volumes. Download as Word, Excel, or PDF.

Draft auto-saved

Conclusion

This capstone project integrates sensor interfacing, solar power harvesting, LoRa wireless communication, and ultra-low-power firmware design into a production-ready environmental monitoring system. The complete BOM under $50 makes this viable for small-run manufacturing.

Next Capstone

In Capstone 2: AI Smart Power Supply, we’ll design a digitally-controlled bench power supply with current sensing, USB-PD negotiation, and an intelligent load management system.