---
title: "LAB15: Level 3 Exercises"
---

## Hardware Deployment Challenges

These exercises require physical hardware (Arduino, ESP32, or Raspberry Pi) and power measurement equipment for energy optimization.

::: {.callout-warning}
## Hardware Required
You need the devices listed in the [Hardware Guide](../../resources/hardware.qmd) to complete these exercises. Specifically for LAB15:
- ESP32 DevKit or Arduino Nano 33 BLE Sense
- INA219 current/power sensor module OR USB power meter
- Breadboard and jumper wires
- Battery pack (optional for real-world testing)
- Multimeter (optional but recommended)
:::

### Exercise 1: Power Measurement Setup with INA219
**Difficulty:** Level 3 - Hardware Integration
**Objective:** Set up accurate power measurement infrastructure using an INA219 sensor to monitor real-time power consumption.

**Task:**
Configure an INA219 current/power sensor to measure the power consumption of your microcontroller during different operational states. This establishes the measurement baseline for all subsequent optimization work.

**Requirements:**

1. **Hardware Connection:**
   - Connect INA219 between power source and MCU
   - INA219 VCC → 3.3V or 5V
   - INA219 GND → Ground
   - INA219 SDA → MCU SDA (I2C data)
   - INA219 SCL → MCU SCL (I2C clock)
   - Power flows through: VCC+ → VCC- terminals

2. **Software Setup:**
   - Install Adafruit INA219 library
   - Configure I2C communication
   - Set appropriate current range (100mA or 400mA)
   - Calibrate sensor if needed

3. **Measurement Tasks:**
   - Measure idle power consumption
   - Measure active processing power
   - Measure deep sleep power
   - Calculate average power over time
   - Log power data for analysis

4. **Validation:**
   - Compare with multimeter readings (±5% accuracy)
   - Verify measurement update rate (≥1Hz)
   - Check for voltage drop across INA219 (<10mV)
   - Validate current range selection

**Expected Outcomes:**
- Accurate power measurements (±2% error)
- Real-time monitoring at 1-10Hz
- Clear distinction between power states
- Data logging capability for analysis

::: {.callout-tip collapse="true"}
## Solution

See full implementation in the PDF book, Chapter 15.

**Key code snippet:**

```cpp
#include <Adafruit_INA219.h>

Adafruit_INA219 ina219;

void setup() {
  Serial.begin(115200);

  if (!ina219.begin()) {
    Serial.println("INA219 not found!");
    while (1);
  }

  ina219.setCalibration_32V_1A();
}

void loop() {
  float voltage = ina219.getBusVoltage_V();
  float current = ina219.getCurrent_mA();
  float power = ina219.getPower_mW();

  Serial.print("Power: ");
  Serial.print(power, 2);
  Serial.println(" mW");

  delay(100);
}
```

**Expected measurements:**
- ESP32 active (240MHz): 70-100mW
- ESP32 idle: 30-50mW
- ESP32 deep sleep: 0.03-0.3mW
:::

### Exercise 2: Deep Sleep Mode Implementation
**Difficulty:** Level 3 - Low Power Programming
**Objective:** Implement deep sleep modes and measure actual power savings.

**Task:**
Configure ESP32 deep sleep with timer and button wake-up. Measure current consumption in each mode.

**Steps:**
1. Implement deep sleep with 10-second timer wake-up
2. Add external wake-up via button
3. Measure deep sleep current (<100μA target)
4. Calculate duty cycle power savings
5. Estimate battery life

**Expected Outcomes:**
- Deep sleep current <100μA
- Wake-up time <500ms
- State preservation across sleep cycles
- 98%+ power savings vs. active mode

::: {.callout-tip collapse="true"}
## Solution

```cpp
#include <esp_sleep.h>

#define SLEEP_DURATION_SEC 10
#define BUTTON_PIN 0

void setup() {
  Serial.begin(115200);

  // Print wake-up reason
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
    Serial.println("Woke by timer");
  }

  // Do work here
  Serial.println("Active for 2 seconds...");
  delay(2000);

  // Configure wake-up sources
  esp_sleep_enable_timer_wakeup(SLEEP_DURATION_SEC * 1000000ULL);
  esp_sleep_enable_ext0_wakeup((gpio_num_t)BUTTON_PIN, 0);

  Serial.println("Going to deep sleep");
  esp_deep_sleep_start();
}

void loop() {
  // Never reached
}
```

**Battery life calculation:**
- Active: 2s @ 80mA
- Sleep: 10s @ 0.05mA
- Average current: (2×80 + 10×0.05) / 12 = 13.4mA
- 1000mAh battery life: 1000/13.4 = 75 hours ≈ 3 days
:::

### Exercise 3: Duty Cycle Optimization
**Difficulty:** Level 3 - System Design
**Objective:** Design optimized duty cycle for sensor application.

**Task:**
Create a battery-powered sensor that wakes every minute to read temperature and transmits every 10 readings. Target: >30 days on 1000mAh battery.

**Requirements:**
- DHT22 or similar sensor
- Wake every 60 seconds
- Transmit batch every 10 minutes
- Max 1 second active time per wake
- Average current <1mA

**Expected Outcomes:**
- Average current <1mA achieved
- Battery life >30 days validated
- Reliable data collection
- Fast wake-up/sleep transitions

::: {.callout-tip collapse="true"}
## Solution

```cpp
#include <esp_sleep.h>
#include <DHT.h>

#define DHTPIN 4
#define DHTTYPE DHT22
#define WAKE_INTERVAL_SEC 60

RTC_DATA_ATTR int readingCount = 0;
RTC_DATA_ATTR float tempBuffer[10];

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);

  // Read sensor
  dht.begin();
  delay(2000);
  float temp = dht.readTemperature();

  // Store in buffer
  tempBuffer[readingCount] = temp;
  readingCount++;

  // Transmit if buffer full
  if (readingCount >= 10) {
    transmitData();
    readingCount = 0;
  }

  // Sleep
  esp_sleep_enable_timer_wakeup(WAKE_INTERVAL_SEC * 1000000ULL);
  esp_deep_sleep_start();
}

void transmitData() {
  // WiFi transmission code
  // Target: <5 seconds active time
}

void loop() {}
```

**Power budget:**
- Sensor read: 1s @ 80mA = 80mAs
- Sleep: 59s @ 0.05mA = 3mAs
- Average per minute: 83mAs / 60s = 1.4mA
- With WiFi every 10 min (5s @ 200mA): still <2mA average
- Battery life: 1000mAh / 2mA = 500 hours ≈ 21 days
:::

### Exercise 4: Dynamic Frequency Scaling
**Difficulty:** Level 3 - Advanced Optimization
**Objective:** Implement CPU frequency scaling to balance performance and power.

**Task:**
Configure ESP32 to dynamically adjust frequency based on workload. Measure power at different frequencies.

**Frequencies to test:**
- 240 MHz: maximum performance
- 160 MHz: balanced
- 80 MHz: power-saving
- 40 MHz: ultra-low power

**Expected Outcomes:**
- 50% power reduction at 80MHz vs 240MHz
- No performance loss for critical tasks
- Smooth frequency transitions
- Energy-optimal task execution

::: {.callout-tip collapse="true"}
## Solution

```cpp
void setCPUFrequency(int freq_mhz) {
  setCpuFrequencyMhz(freq_mhz);
  delay(100); // Stabilize
}

void setup() {
  Serial.begin(115200);

  // Test different frequencies
  testFrequency(240);
  testFrequency(160);
  testFrequency(80);
  testFrequency(40);
}

void testFrequency(int freq) {
  setCPUFrequency(freq);

  // Measure idle power
  Serial.print(freq);
  Serial.println(" MHz idle:");
  measurePower(1000);

  // Measure compute power
  Serial.print(freq);
  Serial.println(" MHz computing:");
  unsigned long start = millis();
  performComputation();
  unsigned long elapsed = millis() - start;

  Serial.print("Time: ");
  Serial.println(elapsed);
  measurePower(1000);
}

void performComputation() {
  volatile float result = 0;
  for (int i = 0; i < 100000; i++) {
    result += sqrt(i);
  }
}
```

**Typical results:**
- 240MHz: 80mW idle, 200mW compute, 100ms task
- 160MHz: 60mW idle, 140mW compute, 150ms task
- 80MHz: 40mW idle, 80mW compute, 300ms task
- Energy per task: ~20mJ, ~21mJ, ~24mJ

**Best strategy:** Use 240MHz for compute-bound tasks (finish fast, sleep sooner)
:::

### Exercise 5: Battery Life Validation
**Difficulty:** Level 3 - Real-World Testing
**Objective:** Validate battery life predictions through actual testing.

**Task:**
Run 24-hour battery test with INA219 logging. Compare measured vs. predicted battery life.

**Test Setup:**
- 1000mAh LiPo battery
- INA219 current monitoring
- SD card data logging
- Real sensor application

**Data to Collect:**
- Voltage over time
- Current over time
- Energy consumption
- Wake/sleep cycle counts

**Expected Outcomes:**
- Measured life within ±20% of prediction
- Complete discharge curve
- Optimization opportunities identified
- Deployment confidence established

::: {.callout-tip collapse="true"}
## Solution

```cpp
#include <SD.h>
#include <Adafruit_INA219.h>

#define SD_CS 5

Adafruit_INA219 ina219;
File logFile;

RTC_DATA_ATTR unsigned long wakeCount = 0;
RTC_DATA_ATTR float totalEnergy = 0;

void setup() {
  wakeCount++;

  ina219.begin();
  SD.begin(SD_CS);

  // Measure current wake cycle
  float power = ina219.getPower_mW();
  float energy = power * 2.0 / 3600;  // 2 seconds in mWh
  totalEnergy += energy;

  // Log to SD card
  logFile = SD.open("battery.csv", FILE_APPEND);
  logFile.print(millis());
  logFile.print(",");
  logFile.print(wakeCount);
  logFile.print(",");
  logFile.print(ina219.getBusVoltage_V());
  logFile.print(",");
  logFile.print(ina219.getCurrent_mA());
  logFile.print(",");
  logFile.println(totalEnergy);
  logFile.close();

  // Sleep
  esp_sleep_enable_timer_wakeup(60 * 1000000ULL);
  esp_deep_sleep_start();
}
```

**Analysis (Python):**
```python
import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('battery.csv')
runtime_hours = data.iloc[-1]['Time'] / 3600000

avg_current = data['Current'].mean()
battery_life = 1000 / avg_current  # hours

print(f"Runtime: {runtime_hours:.1f} hours")
print(f"Avg current: {avg_current:.2f} mA")
print(f"Projected life: {battery_life:.1f} hours ({battery_life/24:.1f} days)")

plt.plot(data['Time']/3600, data['Voltage'])
plt.xlabel('Time (hours)')
plt.ylabel('Battery Voltage (V)')
plt.savefig('discharge_curve.png')
```
:::

---

## Additional Resources

- **Power Optimization Guide**: ESP32 low power modes documentation
- **INA219 Datasheet**: Current sensing techniques
- **Sleep Modes**: ESP32 ULP coprocessor guide
- **Battery Chemistry**: LiPo discharge curves
