Lesson 17

Advanced Sensor Integration & Data Processing

🤖 Second Semester Begins - Advanced Robotics Concepts

🎯 Learning Objectives

🔬 Section 1: Combining Multiple Sensors

Weighted Averages

Sometimes one sensor is more reliable than another. Instead of a simple average, we can give more "weight" to the better sensor. For example, if sensor A is very accurate, we might use 80% of its reading and only 20% from sensor B.

Smart Decision Making

We can program our robot to make smart decisions about which sensors to trust. If one sensor gives a reading that seems way off compared to the others, the robot can ignore it or give it less importance.

Simple Combining Techniques:

Simple Sensor Combining Example:

// Combining multiple distance sensors
class SimpleSensorFusion {
private:
    float lastGoodReading = 0;
    
public:
    // Simple average of two sensors
    float averageTwoSensors(float sensor1, float sensor2) {
        return (sensor1 + sensor2) / 2.0;
    }
    
    // Weighted average - give sensor1 more importance
    float weightedAverage(float sensor1, float sensor2) {
        return (sensor1 * 0.8) + (sensor2 * 0.2);
    }
    
    // Choose the best reading (closest to previous good reading)
    float chooseBestReading(float sensor1, float sensor2) {
        float diff1 = abs(sensor1 - lastGoodReading);
        float diff2 = abs(sensor2 - lastGoodReading);
        
        if (diff1 < diff2) {
            lastGoodReading = sensor1;
            return sensor1;
        } else {
            lastGoodReading = sensor2;
            return sensor2;
        }
    }
};

⚡ Section 2: Cleaning Up Sensor Data

Removing Bad Readings

Sometimes a sensor gives a really bad reading (like measuring 1000cm when everything else is around 20cm). We can detect these "outliers" and ignore them, using the previous good reading instead.

Gradual Updates

Instead of sudden jumps in readings, we can make changes gradually. If a sensor reading changes from 20 to 30, we might update slowly: 21, 22, 23... until we reach 30. This prevents jerky robot movements.

Simple Cleaning Techniques:

Simple Data Cleaning System:

// Simple data cleaning for sensor readings
class DataCleaner {
private:
    static const int BUFFER_SIZE = 5;
    float readings[BUFFER_SIZE];
    int currentIndex = 0;
    float lastGoodReading = 0;
    
public:
    float cleanReading(float newReading) {
        // Step 1: Check if reading seems reasonable
        if (isBadReading(newReading)) {
            Serial.println("Bad reading detected, using last good value");
            return lastGoodReading;
        }
        
        // Step 2: Add to our buffer of recent readings
        readings[currentIndex] = newReading;
        currentIndex = (currentIndex + 1) % BUFFER_SIZE;
        
        // Step 3: Calculate average of recent readings
        float average = calculateAverage();
        
        // Step 4: Make gradual changes to prevent jumps
        float smoothReading = makeGradualChange(average);
        
        lastGoodReading = smoothReading;
        return smoothReading;
    }
    
private:
    bool isBadReading(float reading) {
        // Check if reading is way different from last good reading
        float difference = abs(reading - lastGoodReading);
        return difference > 50; // Adjust this threshold as needed
    }
    
    float calculateAverage() {
        float sum = 0;
        for (int i = 0; i < BUFFER_SIZE; i++) {
            sum += readings[i];
        }
        return sum / BUFFER_SIZE;
    }
    
    float makeGradualChange(float targetReading) {
        // Don't change too quickly - move 20% toward target
        float change = (targetReading - lastGoodReading) * 0.2;
        return lastGoodReading + change;
    }
};

🎯 Section 3: Smart Error Detection and Fixing

Teaching Robots to Fix Themselves

Just like you might adjust your approach when something isn't working right, we can program robots to detect problems and fix them automatically. This makes them more reliable and easier to use.

Automatic Adjustments

Sensors can drift over time or be affected by temperature changes. We can program the robot to notice these changes and adjust automatically, like how your eyes adjust to different lighting conditions.

Calibration Strategies:

Smart Calibration System:

// Simple sensor calibration and error handling
class SmartSensorManager {
private:
    struct SensorCalibration {
        float offset = 0;
        float scale = 1.0;
        float confidence = 1.0;
        unsigned long lastCalibration = 0;
        int errorCount = 0;
    };
    
    SensorCalibration ultrasonicCal;
    SensorCalibration imuCal;
    SensorCalibration compassCal;
    
    static const int CALIBRATION_INTERVAL = 300000; // 5 minutes
    
public:
    void performSystemCalibration() {
        Serial.println("Starting intelligent calibration...");
        
        // Ultrasonic calibration using known reference
        calibrateUltrasonic();
        
        // IMU calibration using gravity reference
        calibrateIMU();
        
        // Compass calibration using magnetic declination
        calibrateCompass();
        
        Serial.println("Calibration complete!");
    }
    
    float getCalibratedReading(int sensorType, float rawValue) {
        SensorCalibration* cal = getSensorCalibration(sensorType);
        
        // Apply calibration
        float calibratedValue = (rawValue + cal->offset) * cal->scale;
        
        // Validate reading
        if (validateReading(sensorType, calibratedValue)) {
            cal->confidence = min(cal->confidence + 0.01, 1.0);
            cal->errorCount = 0;
            return calibratedValue;
        } else {
            cal->confidence = max(cal->confidence - 0.05, 0.1);
            cal->errorCount++;
            
            // Trigger recalibration if too many errors
            if (cal->errorCount > 10) {
                triggerRecalibration(sensorType);
            }
            
            return -1; // Error value
        }
    }
    
private:
    void calibrateUltrasonic() {
        // Multi-point calibration using known distances
        float knownDistances[] = {10, 20, 50, 100}; // cm
        float measurements[4];
        
        for (int i = 0; i < 4; i++) {
            Serial.print("Place object at ");
            Serial.print(knownDistances[i]);
            Serial.println("cm and press button...");
            
            waitForButtonPress();
            measurements[i] = getUltrasonicReading();
            delay(1000);
        }
        
        // Calculate linear calibration coefficients
        calculateLinearCalibration(knownDistances, measurements, 4, &ultrasonicCal);
    }
    
    bool validateReading(int sensorType, float value) {
        // Implement sensor-specific validation logic
        switch (sensorType) {
            case ULTRASONIC:
                return (value > 2 && value < 400); // Valid range for HC-SR04
            case IMU_ACCEL:
                return (abs(value) <= 16); // ±16g range
            case COMPASS:
                return (value >= 0 && value <= 360); // Degree range
            default:
                return true;
        }
    }
};

🛠️ Hands-On Activity: Multi-Sensor Data Logger

Build an Advanced Sensor Monitoring System

Create a comprehensive sensor data logging and analysis system that demonstrates advanced integration techniques.

Activity Steps:

  1. Set up multiple sensors (ultrasonic, IMU, compass, temperature)
  2. Implement the sensor fusion algorithms from Section 1
  3. Add the data processing pipeline from Section 2
  4. Create a calibration routine using Section 3 concepts
  5. Build a real-time data visualization system
  6. Test system performance under different conditions

Complete Implementation:

// Complete multi-sensor data logging system
#include <Wire.h>
#include <MPU6050.h>
#include <HMC5883L.h>

class AdvancedSensorSystem {
private:
    MPU6050 mpu;
    HMC5883L compass;
    
    SensorFusion fusion;
    DataProcessor processor;
    SmartSensorManager calibration;
    
    // Data logging
    struct SensorReading {
        unsigned long timestamp;
        float ultrasonic;
        float accelX, accelY, accelZ;
        float gyroX, gyroY, gyroZ;
        float compass;
        float temperature;
        float fusedHeading;
    };
    
    SensorReading readings[100]; // Circular buffer
    int readingIndex = 0;
    
public:
    void initialize() {
        Serial.begin(115200);
        Wire.begin();
        
        // Initialize sensors
        mpu.initialize();
        compass.initialize();
        
        // Perform initial calibration
        calibration.performSystemCalibration();
        
        Serial.println("Advanced Sensor System Ready!");
    }
    
    void loop() {
        // Collect raw sensor data
        SensorReading current;
        current.timestamp = millis();
        
        // Ultrasonic reading with processing
        float rawUltrasonic = getUltrasonicDistance();
        current.ultrasonic = processor.processReading(rawUltrasonic);
        
        // IMU readings with calibration
        int16_t ax, ay, az, gx, gy, gz;
        mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
        
        current.accelX = calibration.getCalibratedReading(IMU_ACCEL, ax / 16384.0);
        current.accelY = calibration.getCalibratedReading(IMU_ACCEL, ay / 16384.0);
        current.accelZ = calibration.getCalibratedReading(IMU_ACCEL, az / 16384.0);
        
        // Compass reading
        Vector mag = compass.readNormalize();
        current.compass = atan2(mag.YAxis, mag.XAxis) * 180 / PI;
        if (current.compass < 0) current.compass += 360;
        
        // Sensor fusion
        float accelAngle = atan2(current.accelY, current.accelZ) * 180 / PI;
        float gyroRate = gx / 131.0; // Convert to degrees/second
        current.fusedHeading = fusion.fuseIMUData(gyroRate, accelAngle, current.compass);
        
        // Store reading
        readings[readingIndex] = current;
        readingIndex = (readingIndex + 1) % 100;
        
        // Output data for visualization
        outputTelemetry(current);
        
        delay(50); // 20Hz update rate
    }
    
private:
    void outputTelemetry(const SensorReading& reading) {
        Serial.print("TIME:");
        Serial.print(reading.timestamp);
        Serial.print(",ULTRA:");
        Serial.print(reading.ultrasonic);
        Serial.print(",ACCEL:");
        Serial.print(reading.accelX); Serial.print(",");
        Serial.print(reading.accelY); Serial.print(",");
        Serial.print(reading.accelZ);
        Serial.print(",COMPASS:");
        Serial.print(reading.compass);
        Serial.print(",FUSED:");
        Serial.println(reading.fusedHeading);
    }
};

AdvancedSensorSystem sensorSystem;

void setup() {
    sensorSystem.initialize();
}

void loop() {
    sensorSystem.loop();
}

📝 Assessment & Homework

Lesson 17 Assignments

Programming Challenges:

  1. Sensor Fusion Challenge: Implement a Kalman filter for position estimation using accelerometer and gyroscope data
  2. Data Processing Project: Create an adaptive filtering system that automatically adjusts filter parameters based on noise levels
  3. Calibration System: Design a self-calibrating sensor system that can detect and correct for sensor drift
  4. Performance Analysis: Compare the accuracy of different fusion algorithms using controlled test scenarios

Research Assignment:

Research and write a 2-page report on "Advanced Sensor Technologies in Modern Robotics." Include examples of LIDAR, computer vision, and AI-enhanced sensor systems. Due next class.

Lab Report:

Document your multi-sensor data logger implementation, including calibration procedures, performance metrics, and analysis of sensor fusion accuracy under different conditions.

← Lesson 16: Advanced Projects 📚 Second Semester Overview Next: Lesson 18 →