Skip to main content

ESP32 И ЭЛЕКТРОНИКА

📋 ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ К МОДУЛЮ 5: “ESP32 И ЭЛЕКТРОНИКА”

Углубленные ресурсы для освоения программирования и электроники дронов


🔬 РАЗДЕЛ A: ЭКСПЕРИМЕНТАЛЬНАЯ ЛАБОРАТОРИЯ

1. Домашняя лаборатория электроники

1.1 Набор “Электроника дронов своими руками”

🏠 СОЗДАНИЕ ДОМАШНЕЙ ЛАБОРАТОРИИ:

БАЗОВОЕ ОБОРУДОВАНИЕ:
□ Макетная плата (breadboard) 830 точек
□ ESP32 Dev Kit (2-3 штуки для экспериментов)
□ Набор резисторов (10Ом - 1МОм)
□ Конденсаторы (1nF - 1000uF)
□ Светодиоды разных цветов (50+ штук)
□ Кнопки и переключатели
□ Потенциометры (1кОм, 10кОм, 100кОм)
□ Провода для макетирования (M-M, M-F, F-F)

ИЗМЕРИТЕЛЬНЫЕ ПРИБОРЫ:
□ Цифровой мультиметр с автоматическим выбором диапазонов
□ USB осциллограф (или DSO138 kit)
□ Логический анализатор на 8 каналов
□ Генератор функций (XR2206 kit)
□ Источник питания 0-30V 0-3A (регулируемый)

СПЕЦИАЛИЗИРОВАННЫЕ МОДУЛИ:
□ IMU модули (MPU6050, MPU9250, ICM20948)
□ GPS модули (NEO-8M, NEO-M9N)
□ Барометры (BMP280, MS5611)
□ Дальномеры (VL53L0X, HC-SR04)
□ ESC модули для тестирования
□ Серводвигатели различных размеров
□ Моторы BLDC с энкодерами

СТЕНДЫ ДЛЯ ЭКСПЕРИМЕНТОВ:
□ Поворотный стол для тестирования IMU
□ Камера давления для тестирования барометров
□ Экранированная камера для GPS тестов
□ Вибростенд для анализа помехоустойчивости

1.2 Продвинутая схемотехника

⚡ ПРАКТИЧЕСКИЕ СХЕМЫ ДЛЯ ДРОНОВ:

СХЕМА 1: УНИВЕРСАЛЬНЫЙ ФИЛЬТР ПИТАНИЯ

Проблема: Импульсные помехи от ESC влияют на датчики

Решение: VCC (нестабильное) ──┬── L1 (10uH) ──┬── VCC_CLEAN │ │ └── C1 (1000uF) ├── C2 (100nF) │ GND ─────────────────────────────────┴─── GND

L1 - дроссель для фильтрации ВЧ помех C1 - сглаживание низкочастотных пульсаций
C2 - подавление высокочастотных помех

Практическая реализация:

// Тестирование качества питания
void testPowerQuality() {
  int samples = 1000;
  float sum = 0, sumSquares = 0;
  
  for(int i = 0; i < samples; i++) {
    float voltage = analogRead(A0) * (3.3/4095.0);
    sum += voltage;
    sumSquares += voltage * voltage;
    delayMicroseconds(10);  // 100kHz sampling
  }
  
  float mean = sum / samples;
  float variance = (sumSquares / samples) - (mean * mean);
  float ripple = sqrt(variance);
  
  Serial.printf("Среднее напряжение: %.3fV\n", mean);
  Serial.printf("Пульсации: %.1fmV RMS\n", ripple * 1000);
  Serial.printf("Качество питания: %s\n", 
    ripple < 0.01 ? "ОТЛИЧНО" : 
    ripple < 0.05 ? "ХОРОШО" : "ТРЕБУЕТ ФИЛЬТРАЦИИ");
}

СХЕМА 2: ЗАЩИТА ОТ ПЕРЕПОЛЮСОВКИ

Проблема: Неправильное подключение батареи может сжечь электронику

Решение с MOSFET:
BAT+ ──── DRAIN │ │ SOURCE ──── VCC_PROTECTED
              │ P-MOSFET │
              │    │    │
              └────┘    │
                   │    │
BAT- ──────────────┴────┴──── GND

Альтернатива с диодом Шоттки:
BAT+ ──── ANODE │▷│ CATHODE ──── VCC_PROTECTED
               DIODE
BAT- ───────────────────────── GND

Сравнение решений:
- MOSFET: Нет падения напряжения, но сложнее
- Диод: Простота, но потеря 0.3-0.7V

СХЕМА 3: РАЗВЯЗКА ЦИФРОВЫХ И АНАЛОГОВЫХ ЦЕПЕЙ

Проблема: Цифровые помехи влияют на точность АЦП

Решение:
VCC_DIGITAL ──┬── ESP32 Digital VCC
              │
              ├── FERRITE BEAD ──── VCC_ANALOG ──── ESP32 Analog VCC
              │
GND_DIGITAL ──┼── ESP32 Digital GND  
              │
              └── SINGLE POINT ──── GND_ANALOG ──── ESP32 Analog GND

Практическая проверка:
```cpp
void testADCNoise() {
  // Тест без нагрузки на цифровые линии
  digitalWrite(LED_BUILTIN, LOW);
  int quietReading = analogRead(A0);
  
  // Тест с активной цифровой нагрузкой
  for(int i = 0; i < 100; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(LED_BUILTIN, LOW);
    delayMicroseconds(10);
  }
  int noisyReading = analogRead(A0);
  
  int noiseDifference = abs(noisyReading - quietReading);
  Serial.printf("Шум АЦП от цифровых цепей: %d LSB\n", noiseDifference);
  Serial.printf("Качество развязки: %s\n", 
    noiseDifference < 5 ? "ОТЛИЧНО" : 
    noiseDifference < 20 ? "ПРИЕМЛЕМО" : "ПЛОХО");
}

2. Продвинутая диагностика и отладка

2.1 Системы мониторинга в реальном времени

// =====================================
// СИСТЕМА ДИАГНОСТИКИ ESP32 ДРОНА
// =====================================

#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <SPIFFS.h>

class DroneDebugger {
private:
  WebServer server;
  unsigned long lastUpdate;
  
  struct SystemMetrics {
    float cpuUsage;
    float memoryUsage;
    float coreTemperature;
    int wifiSignal;
    float batteryVoltage;
    float powerConsumption;
  } metrics;
  
  struct SensorHealth {
    bool imuStatus;
    bool gpsStatus;
    bool baroStatus;
    float imuTemp;
    int gpsSatellites;
    float baroPressure;
  } sensors;

public:
  DroneDebugger() : server(80) {}
  
  void begin() {
    SPIFFS.begin();
    setupWebServer();
    server.begin();
    Serial.println("Debug server started on port 80");
  }
  
  void update() {
    server.handleClient();
    
    if (millis() - lastUpdate > 100) {  // 10Hz update
      updateMetrics();
      checkSensorHealth();
      lastUpdate = millis();
    }
  }
  
private:
  void setupWebServer() {
    // Главная страница
    server.on("/", [this]() {
      server.send(200, "text/html", getMainPage());
    });
    
    // API для получения метрик
    server.on("/api/metrics", [this]() {
      DynamicJsonDocument doc(1024);
      
      doc["cpu_usage"] = metrics.cpuUsage;
      doc["memory_usage"] = metrics.memoryUsage;
      doc["temperature"] = metrics.coreTemperature;
      doc["wifi_signal"] = metrics.wifiSignal;
      doc["battery_voltage"] = metrics.batteryVoltage;
      doc["power_consumption"] = metrics.powerConsumption;
      
      doc["imu_status"] = sensors.imuStatus;
      doc["gps_status"] = sensors.gpsStatus;
      doc["baro_status"] = sensors.baroStatus;
      doc["imu_temp"] = sensors.imuTemp;
      doc["gps_satellites"] = sensors.gpsSatellites;
      doc["baro_pressure"] = sensors.baroPressure;
      
      String json;
      serializeJson(doc, json);
      server.send(200, "application/json", json);
    });
    
    // API для логов
    server.on("/api/logs", [this]() {
      File logFile = SPIFFS.open("/flight.log", "r");
      if (logFile) {
        server.streamFile(logFile, "text/plain");
        logFile.close();
      } else {
        server.send(404, "text/plain", "Log file not found");
      }
    });
  }
  
  void updateMetrics() {
    // CPU usage (приблизительно)
    static unsigned long lastIdleTime = 0;
    unsigned long currentTime = micros();
    unsigned long busyTime = currentTime - lastIdleTime;
    metrics.cpuUsage = constrain(busyTime / 1000.0, 0, 100);
    lastIdleTime = currentTime;
    
    // Memory usage
    metrics.memoryUsage = (ESP.getHeapSize() - ESP.getFreeHeap()) * 100.0 / ESP.getHeapSize();
    
    // Core temperature (если доступно)
    metrics.coreTemperature = temperatureRead();
    
    // WiFi signal
    metrics.wifiSignal = WiFi.RSSI();
    
    // Battery voltage (через делитель напряжения)
    int adcValue = analogRead(A0);
    metrics.batteryVoltage = adcValue * (3.3 / 4095.0) * 3.7;  // Коэффициент делителя
    
    // Power consumption (расчетный)
    metrics.powerConsumption = metrics.batteryVoltage * estimateCurrentConsumption();
  }
  
  float estimateCurrentConsumption() {
    // Базовое потребление ESP32
    float current = 240;  // mA для ESP32 на полной нагрузке
    
    // Добавляем потребление WiFi
    if (WiFi.status() == WL_CONNECTED) {
      current += 120;  // mA для активного WiFi
    }
    
    // Добавляем потребление датчиков
    current += 50;  // mA для IMU + GPS + Baro
    
    return current / 1000.0;  // Конвертируем в амперы
  }
  
  void checkSensorHealth() {
    // Проверка IMU
    sensors.imuStatus = checkIMUHealth();
    
    // Проверка GPS
    sensors.gpsStatus = checkGPSHealth();
    
    // Проверка барометра
    sensors.baroStatus = checkBaroHealth();
  }
  
  bool checkIMUHealth() {
    // Здесь должна быть реальная проверка IMU
    // Для примера возвращаем случайное значение
    return random(0, 100) > 10;  // 90% uptime
  }
  
  bool checkGPSHealth() {
    sensors.gpsSatellites = random(4, 12);  // Симуляция спутников
    return sensors.gpsSatellites >= 4;
  }
  
  bool checkBaroHealth() {
    sensors.baroPressure = 1013.25 + random(-100, 100) / 10.0;
    return abs(sensors.baroPressure - 1013.25) < 50;  // Разумный диапазон
  }
  
  String getMainPage() {
    return R"HTML(
<!DOCTYPE html>
<html>
<head>
    <title>ESP32 Drone Debugger</title>
    <meta charset="utf-8">
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }
        .container { max-width: 1200px; margin: 0 auto; }
        .card { background: white; padding: 20px; margin: 10px; border-radius: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        .metric { display: inline-block; margin: 10px; padding: 15px; background: #e8f4f8; border-radius: 5px; min-width: 150px; }
        .metric-value { font-size: 24px; font-weight: bold; color: #333; }
        .metric-label { font-size: 12px; color: #666; }
        .status-ok { color: #4CAF50; }
        .status-warning { color: #FF9800; }
        .status-error { color: #F44336; }
        #chart { width: 100%; height: 300px; background: white; border: 1px solid #ddd; }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <div class="container">
        <h1>🚁 ESP32 Drone Debug Console</h1>
        
        <div class="card">
            <h2>System Metrics</h2>
            <div id="systemMetrics"></div>
        </div>
        
        <div class="card">
            <h2>Sensor Status</h2>
            <div id="sensorStatus"></div>
        </div>
        
        <div class="card">
            <h2>Real-time Data</h2>
            <canvas id="chart"></canvas>
        </div>
        
        <div class="card">
            <h2>Actions</h2>
            <button onclick="downloadLogs()">📥 Download Logs</button>
            <button onclick="rebootSystem()">🔄 Reboot ESP32</button>
            <button onclick="calibrateSensors()">🎯 Calibrate Sensors</button>
        </div>
    </div>
    
    <script>
        let chart;
        let dataPoints = [];
        
        function initChart() {
            const ctx = document.getElementById('chart').getContext('2d');
            chart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [{
                        label: 'CPU Usage %',
                        data: [],
                        borderColor: 'rgb(75, 192, 192)',
                        tension: 0.1
                    }, {
                        label: 'Memory Usage %',
                        data: [],
                        borderColor: 'rgb(255, 99, 132)',
                        tension: 0.1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        y: { beginAtZero: true, max: 100 }
                    }
                }
            });
        }
        
        function updateMetrics() {
            fetch('/api/metrics')
                .then(response => response.json())
                .then(data => {
                    // Update system metrics
                    document.getElementById('systemMetrics').innerHTML = `
                        <div class="metric">
                            <div class="metric-value">${data.cpu_usage.toFixed(1)}%</div>
                            <div class="metric-label">CPU Usage</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value">${data.memory_usage.toFixed(1)}%</div>
                            <div class="metric-label">Memory Usage</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value">${data.temperature.toFixed(1)}°C</div>
                            <div class="metric-label">Temperature</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value">${data.wifi_signal} dBm</div>
                            <div class="metric-label">WiFi Signal</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value">${data.battery_voltage.toFixed(2)}V</div>
                            <div class="metric-label">Battery</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value">${data.power_consumption.toFixed(2)}A</div>
                            <div class="metric-label">Current</div>
                        </div>
                    `;
                    
                    // Update sensor status
                    document.getElementById('sensorStatus').innerHTML = `
                        <div class="metric">
                            <div class="metric-value ${data.imu_status ? 'status-ok' : 'status-error'}">
                                ${data.imu_status ? '✅' : '❌'}
                            </div>
                            <div class="metric-label">IMU (${data.imu_temp.toFixed(1)}°C)</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value ${data.gps_status ? 'status-ok' : 'status-error'}">
                                ${data.gps_status ? '✅' : '❌'}
                            </div>
                            <div class="metric-label">GPS (${data.gps_satellites} sats)</div>
                        </div>
                        <div class="metric">
                            <div class="metric-value ${data.baro_status ? 'status-ok' : 'status-error'}">
                                ${data.baro_status ? '✅' : '❌'}
                            </div>
                            <div class="metric-label">Barometer (${data.baro_pressure.toFixed(1)} hPa)</div>
                        </div>
                    `;
                    
                    // Update chart
                    const now = new Date().toLocaleTimeString();
                    chart.data.labels.push(now);
                    chart.data.datasets[0].data.push(data.cpu_usage);
                    chart.data.datasets[1].data.push(data.memory_usage);
                    
                    // Keep only last 20 points
                    if (chart.data.labels.length > 20) {
                        chart.data.labels.shift();
                        chart.data.datasets[0].data.shift();
                        chart.data.datasets[1].data.shift();
                    }
                    
                    chart.update('none');
                })
                .catch(error => console.error('Error fetching metrics:', error));
        }
        
        function downloadLogs() {
            window.open('/api/logs', '_blank');
        }
        
        function rebootSystem() {
            if (confirm('Reboot ESP32? This will disconnect the debug session.')) {
                fetch('/api/reboot', { method: 'POST' });
            }
        }
        
        function calibrateSensors() {
            if (confirm('Start sensor calibration? Keep the drone stationary.')) {
                fetch('/api/calibrate', { method: 'POST' });
            }
        }
        
        // Initialize
        initChart();
        updateMetrics();
        setInterval(updateMetrics, 1000);  // Update every second
    </script>
</body>
</html>
    )HTML";
  }
};

// Использование
DroneDebugger debugger;

void setup() {
  Serial.begin(115200);
  WiFi.begin("YourWiFi", "password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }
  debugger.begin();
}

void loop() {
  debugger.update();
  // Ваш основной код дрона здесь
}

2.2 Автоматизированное тестирование

// =====================================
// СИСТЕМА АВТОМАТИЧЕСКОГО ТЕСТИРОВАНИЯ
// =====================================

class AutomatedTester {
private:
  struct TestResult {
    String testName;
    bool passed;
    String details;
    unsigned long executionTime;
  };
  
  std::vector<TestResult> testResults;
  
public:
  void runAllTests() {
    Serial.println("=== AUTOMATED TESTING STARTED ===");
    
    testResults.clear();
    
    // Hardware tests
    runTest("Power Supply Test", testPowerSupply);
    runTest("GPIO Functionality", testGPIO);
    runTest("ADC Accuracy", testADC);
    runTest("PWM Generation", testPWM);
    
    // Communication tests
    runTest("I2C Bus Scan", testI2C);
    runTest("SPI Communication", testSPI);
    runTest("UART Loopback", testUART);
    
    // Sensor tests
    runTest("IMU Calibration", testIMU);
    runTest("GPS Signal", testGPS);
    runTest("Barometer", testBarometer);
    
    // Performance tests
    runTest("CPU Performance", testCPUPerformance);
    runTest("Memory Stress", testMemoryStress);
    runTest("Real-time Constraints", testRealTime);
    
    printTestReport();
  }
  
private:
  void runTest(String name, bool (*testFunction)()) {
    Serial.printf("Running test: %s... ", name.c_str());
    
    unsigned long startTime = micros();
    bool result = testFunction();
    unsigned long executionTime = micros() - startTime;
    
    TestResult testResult;
    testResult.testName = name;
    testResult.passed = result;
    testResult.executionTime = executionTime;
    
    testResults.push_back(testResult);
    
    Serial.println(result ? "PASS" : "FAIL");
  }
  
  static bool testPowerSupply() {
    // Тест стабильности напряжений
    float vcc3v3 = analogRead(A1) * (3.3 / 4095.0);
    float vcc5v = analogRead(A2) * (3.3 / 4095.0) * 2.0;  // Делитель 1:2
    
    bool vcc3v3_ok = (vcc3v3 > 3.2 && vcc3v3 < 3.4);
    bool vcc5v_ok = (vcc5v > 4.8 && vcc5v < 5.2);
    
    return vcc3v3_ok && vcc5v_ok;
  }
  
  static bool testGPIO() {
    // Тест цифровых входов/выходов
    const int testPin1 = 2;
    const int testPin2 = 4;
    
    pinMode(testPin1, OUTPUT);
    pinMode(testPin2, INPUT_PULLUP);
    
    // Соединить testPin1 и testPin2 перемычкой для теста
    digitalWrite(testPin1, HIGH);
    delay(10);
    bool test1 = digitalRead(testPin2) == HIGH;
    
    digitalWrite(testPin1, LOW);
    delay(10);
    bool test2 = digitalRead(testPin2) == LOW;
    
    return test1 && test2;
  }
  
  static bool testADC() {
    // Тест точности АЦП
    const int samples = 100;
    float sum = 0;
    
    // Подключить известное напряжение к A0
    for (int i = 0; i < samples; i++) {
      sum += analogRead(A0);
      delay(1);
    }
    
    float average = sum / samples;
    float voltage = average * (3.3 / 4095.0);
    
    // Проверяем что напряжение в разумных пределах
    return (voltage > 0.1 && voltage < 3.2);
  }
  
  static bool testPWM() {
    // Тест генерации PWM
    const int pwmPin = 18;
    
    ledcSetup(0, 1000, 8);  // 1kHz, 8-bit
    ledcAttachPin(pwmPin, 0);
    
    // Тест различных скважностей
    int dutyCycles[] = {0, 64, 128, 192, 255};
    
    for (int duty : dutyCycles) {
      ledcWrite(0, duty);
      delay(100);
      
      // Здесь можно измерить реальную скважность
      // Для упрощения считаем тест пройденным
    }
    
    return true;
  }
  
  static bool testI2C() {
    // Сканирование I2C шины
    Wire.begin();
    int devicesFound = 0;
    
    for (int address = 1; address < 127; address++) {
      Wire.beginTransmission(address);
      int error = Wire.endTransmission();
      
      if (error == 0) {
        devicesFound++;
        Serial.printf("I2C device found at 0x%02X\n", address);
      }
    }
    
    return devicesFound > 0;  // Хотя бы одно устройство найдено
  }
  
  static bool testSPI() {
    // Тест SPI loopback (MOSI соединен с MISO)
    SPI.begin();
    
    byte testData[] = {0xAA, 0x55, 0xFF, 0x00};
    bool success = true;
    
    for (byte data : testData) {
      byte received = SPI.transfer(data);
      if (received != data) {
        success = false;
        break;
      }
    }
    
    return success;
  }
  
  static bool testUART() {
    // Тест UART loopback (TX соединен с RX)
    Serial1.begin(115200);
    
    String testString = "UART_TEST_123";
    Serial1.print(testString);
    delay(100);
    
    String received = "";
    while (Serial1.available()) {
      received += (char)Serial1.read();
    }
    
    return received == testString;
  }
  
  static bool testIMU() {
    // Тест IMU (проверка базовой функциональности)
    // Здесь должна быть реальная инициализация IMU
    // Для примера возвращаем true
    return true;
  }
  
  static bool testGPS() {
    // Тест GPS модуля
    // Проверяем получение данных
    return true;  // Упрощенная реализация
  }
  
  static bool testBarometer() {
    // Тест барометра
    return true;  // Упрощенная реализация
  }
  
  static bool testCPUPerformance() {
    // Тест производительности CPU
    unsigned long startTime = micros();
    
    // Выполняем вычислительно-интенсивную задачу
    float result = 0;
    for (int i = 0; i < 10000; i++) {
      result += sin(i * 0.01) * cos(i * 0.01);
    }
    
    unsigned long executionTime = micros() - startTime;
    
    // Ожидаемое время выполнения на ESP32
    return executionTime < 100000;  // Меньше 100ms
  }
  
  static bool testMemoryStress() {
    // Тест памяти
    size_t freeHeapBefore = ESP.getFreeHeap();
    
    // Выделяем и освобождаем память
    void* ptr = malloc(10000);
    if (ptr == nullptr) return false;
    
    free(ptr);
    
    size_t freeHeapAfter = ESP.getFreeHeap();
    
    // Проверяем что память освободилась
    return (freeHeapAfter >= freeHeapBefore - 100);  // Допуск 100 байт
  }
  
  static bool testRealTime() {
    // Тест соблюдения временных ограничений
    unsigned long times[100];
    
    for (int i = 0; i < 100; i++) {
      unsigned long start = micros();
      delay(10);  // Задержка 10ms
      times[i] = micros() - start;
    }
    
    // Проверяем что все задержки в пределах ±1ms
    for (int i = 0; i < 100; i++) {
      if (times[i] < 9000 || times[i] > 11000) {
        return false;
      }
    }
    
    return true;
  }
  
  void printTestReport() {
    Serial.println("\n=== TEST REPORT ===");
    
    int passed = 0;
    int total = testResults.size();
    
    for (const auto& result : testResults) {
      Serial.printf("%-20s: %s (%lu μs)\n", 
        result.testName.c_str(),
        result.passed ? "PASS" : "FAIL",
        result.executionTime);
      
      if (result.passed) passed++;
    }
    
    Serial.printf("\nSUMMARY: %d/%d tests passed (%.1f%%)\n", 
      passed, total, (float)passed * 100.0 / total);
    
    if (passed == total) {
      Serial.println("🎉 ALL TESTS PASSED! System is ready for flight.");
    } else {
      Serial.println("⚠️  SOME TESTS FAILED! Check hardware connections.");
    }
  }
};

🛠️ РАЗДЕЛ B: РАСШИРЕННЫЕ БИБЛИОТЕКИ И ФРЕЙМВОРКИ

3. Специализированные библиотеки для дронов

3.1 Продвинутая работа с сенсорами

// =====================================
// УНИВЕРСАЛЬНАЯ БИБЛИОТЕКА ДАТЧИКОВ
// =====================================

#include <Wire.h>
#include <SPI.h>

class UniversalSensorLib {
public:
  enum SensorType {
    IMU_MPU6050,
    IMU_MPU9250, 
    IMU_ICM20948,
    BARO_BMP280,
    BARO_MS5611,
    GPS_UBLOX,
    RANGEFINDER_VL53L0X,
    COMPASS_HMC5883L
  };
  
  struct SensorData {
    float gyroX, gyroY, gyroZ;      // град/с
    float accelX, accelY, accelZ;   // g
    float magX, magY, magZ;         // Гаусс
    float pressure;                 // Па
    float temperature;              // °C
    float altitude;                 // м
    float latitude, longitude;      // градусы
    int satellites;
    float distance;                 // м (дальномер)
    unsigned long timestamp;       // мс
    bool dataValid;
  };
  
private:
  std::vector<SensorType> activeSensors;
  SensorData lastData;
  
public:
  bool initSensor(SensorType type, int address = 0) {
    switch (type) {
      case IMU_MPU6050:
        return initMPU6050(address ? address : 0x68);
      case IMU_MPU9250:
        return initMPU9250(address ? address : 0x68);
      case BARO_BMP280:
        return initBMP280(address ? address : 0x76);
      // ... другие датчики
    }
    return false;
  }
  
  SensorData readAllSensors() {
    SensorData data = {};
    data.timestamp = millis();
    data.dataValid = true;
    
    for (SensorType sensor : activeSensors) {
      switch (sensor) {
        case IMU_MPU6050:
          readMPU6050(data);
          break;
        case BARO_BMP280:
          readBMP280(data);
          break;
        // ... другие датчики
      }
    }
    
    lastData = data;
    return data;
  }
  
  // Автоматическая калибровка
  bool calibrateSensors(int samples = 1000) {
    Serial.println("Starting sensor calibration...");
    Serial.println("Keep the drone stationary!");
    
    // Калибровка гироскопа (определение offset)
    float gyroOffsetX = 0, gyroOffsetY = 0, gyroOffsetZ = 0;
    
    for (int i = 0; i < samples; i++) {
      SensorData data = readAllSensors();
      gyroOffsetX += data.gyroX;
      gyroOffsetY += data.gyroY;
      gyroOffsetZ += data.gyroZ;
      
      if (i % 100 == 0) {
        Serial.printf("Calibration progress: %d%%\n", i * 100 / samples);
      }
      
      delay(10);
    }
    
    gyroOffsetX /= samples;
    gyroOffsetY /= samples;
    gyroOffsetZ /= samples;
    
    // Сохранение калибровочных данных
    saveCalibrationData(gyroOffsetX, gyroOffsetY, gyroOffsetZ);
    
    Serial.println("Calibration complete!");
    return true;
  }
  
  // Диагностика датчиков
  void runDiagnostics() {
    Serial.println("=== SENSOR DIAGNOSTICS ===");
    
    for (SensorType sensor : activeSensors) {
      switch (sensor) {
        case IMU_MPU6050:
          diagnoseMPU6050();
          break;
        case BARO_BMP280:
          diagnoseBMP280();
          break;
        // ... другие датчики
      }
    }
  }
  
private:
  bool initMPU6050(int address) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() != 0) {
      Serial.println("MPU6050 not found!");
      return false;
    }
    
    // Инициализация MPU6050
    writeRegister(address, 0x6B, 0x00);  // Wake up
    writeRegister(address, 0x1A, 0x03);  // Low pass filter
    writeRegister(address, 0x1B, 0x08);  // Gyro ±500°/s
    writeRegister(address, 0x1C, 0x08);  // Accel ±4g
    
    activeSensors.push_back(IMU_MPU6050);
    Serial.println("MPU6050 initialized");
    return true;
  }
  
  void readMPU6050(SensorData& data) {
    const int MPU6050_ADDR = 0x68;
    
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(0x3B);  // Starting register
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 14, true);
    
    // Чтение сырых данных
    int16_t rawAccelX = Wire.read() << 8 | Wire.read();
    int16_t rawAccelY = Wire.read() << 8 | Wire.read();
    int16_t rawAccelZ = Wire.read() << 8 | Wire.read();
    int16_t rawTemp   = Wire.read() << 8 | Wire.read();
    int16_t rawGyroX  = Wire.read() << 8 | Wire.read();
    int16_t rawGyroY  = Wire.read() << 8 | Wire.read();
    int16_t rawGyroZ  = Wire.read() << 8 | Wire.read();
    
    // Преобразование в физические единицы
    data.accelX = rawAccelX / 8192.0;  // ±4g range
    data.accelY = rawAccelY / 8192.0;
    data.accelZ = rawAccelZ / 8192.0;
    
    data.gyroX = rawGyroX / 65.5;      // ±500°/s range
    data.gyroY = rawGyroY / 65.5;
    data.gyroZ = rawGyroZ / 65.5;
    
    data.temperature = rawTemp / 340.0 + 36.53;
  }
  
  bool initBMP280(int address) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() != 0) {
      Serial.println("BMP280 not found!");
      return false;
    }
    
    // Чтение ID
    uint8_t chipID = readRegister(address, 0xD0);
    if (chipID != 0x58) {
      Serial.printf("Wrong BMP280 ID: 0x%02X\n", chipID);
      return false;
    }
    
    // Настройка BMP280
    writeRegister(address, 0xF4, 0x27);  // Normal mode, temp and pressure oversampling
    writeRegister(address, 0xF5, 0xA0);  // Config: standby time, filter
    
    activeSensors.push_back(BARO_BMP280);
    Serial.println("BMP280 initialized");
    return true;
  }
  
  void readBMP280(SensorData& data) {
    const int BMP280_ADDR = 0x76;
    
    // Чтение сырых данных давления и температуры
    Wire.beginTransmission(BMP280_ADDR);
    Wire.write(0xF7);  // Pressure MSB
    Wire.endTransmission(false);
    Wire.requestFrom(BMP280_ADDR, 6, true);
    
    uint32_t rawPressure = (Wire.read() << 12) | (Wire.read() << 4) | (Wire.read() >> 4);
    uint32_t rawTemp = (Wire.read() << 12) | (Wire.read() << 4) | (Wire.read() >> 4);
    
    // Компенсация (упрощенная)
    data.temperature = rawTemp / 16384.0 - 15.0;  // Приблизительная формула
    data.pressure = rawPressure / 256.0;          // Приблизительная формула
    
    // Расчет высоты по барометрической формуле
    data.altitude = 44330.0 * (1.0 - pow(data.pressure / 101325.0, 0.1903));
  }
  
  void diagnoseMPU6050() {
    Serial.println("MPU6050 Diagnostics:");
    
    // Проверка температуры
    SensorData data = readAllSensors();
    Serial.printf("  Temperature: %.1f°C ", data.temperature);
    if (data.temperature > -40 && data.temperature < 85) {
      Serial.println("OK");
    } else {
      Serial.println("OUT OF RANGE");
    }
    
    // Проверка уровня шума
    float gyroNoise = 0;
    for (int i = 0; i < 100; i++) {
      SensorData sample = readAllSensors();
      gyroNoise += abs(sample.gyroX) + abs(sample.gyroY) + abs(sample.gyroZ);
      delay(10);
    }
    gyroNoise /= 100;
    
    Serial.printf("  Gyro noise level: %.2f°/s ", gyroNoise);
    if (gyroNoise < 1.0) {
      Serial.println("GOOD");
    } else if (gyroNoise < 5.0) {
      Serial.println("ACCEPTABLE");
    } else {
      Serial.println("HIGH - CHECK MOUNTING");
    }
  }
  
  void diagnoseBMP280() {
    Serial.println("BMP280 Diagnostics:");
    
    SensorData data = readAllSensors();
    
    Serial.printf("  Pressure: %.1f Pa ", data.pressure);
    if (data.pressure > 80000 && data.pressure < 120000) {
      Serial.println("REASONABLE");
    } else {
      Serial.println("OUT OF EXPECTED RANGE");
    }
    
    Serial.printf("  Calculated altitude: %.1f m\n", data.altitude);
  }
  
  // Вспомогательные функции
  void writeRegister(int address, uint8_t reg, uint8_t value) {
    Wire.beginTransmission(address);
    Wire.write(reg);
    Wire.write(value);
    Wire.endTransmission();
  }
  
  uint8_t readRegister(int address, uint8_t reg) {
    Wire.beginTransmission(address);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(address, 1, true);
    return Wire.read();
  }
  
  void saveCalibrationData(float gx, float gy, float gz) {
    // Сохранение в EEPROM или SPIFFS
    // Упрощенная реализация
  }
};

// Пример использования
UniversalSensorLib sensors;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  
  // Инициализация датчиков
  sensors.initSensor(UniversalSensorLib::IMU_MPU6050);
  sensors.initSensor(UniversalSensorLib::BARO_BMP280);
  
  // Калибровка
  sensors.calibrateSensors();
  
  // Диагностика
  sensors.runDiagnostics();
}

void loop() {
  UniversalSensorLib::SensorData data = sensors.readAllSensors();
  
  Serial.printf("Gyro: %.2f, %.2f, %.2f | ", data.gyroX, data.gyroY, data.gyroZ);
  Serial.printf("Accel: %.2f, %.2f, %.2f | ", data.accelX, data.accelY, data.accelZ);
  Serial.printf("Alt: %.1fm\n", data.altitude);
  
  delay(100);
}

3.2 Фреймворк для создания автопилотов

// =====================================
// МОДУЛЬНЫЙ ФРЕЙМВОРК АВТОПИЛОТА
// =====================================

class AutopilotFramework {
public:
  // Базовый класс для модулей
  class Module {
  public:
    virtual void init() = 0;
    virtual void update(float dt) = 0;
    virtual bool isHealthy() = 0;
    virtual String getName() = 0;
  };
  
  // Модуль сбора данных датчиков
  class SensorModule : public Module {
  private:
    UniversalSensorLib sensors;
    
  public:
    UniversalSensorLib::SensorData currentData;
    
    void init() override {
      sensors.initSensor(UniversalSensorLib::IMU_MPU6050);
      sensors.initSensor(UniversalSensorLib::BARO_BMP280);
    }
    
    void update(float dt) override {
      currentData = sensors.readAllSensors();
    }
    
    bool isHealthy() override {
      return currentData.dataValid && 
             (millis() - currentData.timestamp) < 100;
    }
    
    String getName() override { return "SensorModule"; }
  };
  
  // Модуль оценки состояния
  class StateEstimatorModule : public Module {
  public:
    struct State {
      float roll, pitch, yaw;        // углы ориентации
      float rollRate, pitchRate, yawRate;  // угловые скорости
      float x, y, z;                 // позиция
      float vx, vy, vz;              // скорости
      float ax, ay, az;              // ускорения
    } currentState;
    
  private:
    SensorModule* sensorModule;
    
  public:
    StateEstimatorModule(SensorModule* sensors) : sensorModule(sensors) {}
    
    void init() override {
      // Инициализация фильтров
    }
    
    void update(float dt) override {
      if (!sensorModule || !sensorModule->isHealthy()) return;
      
      auto& data = sensorModule->currentData;
      
      // Комплементарный фильтр для углов
      updateAttitudeEstimate(data, dt);
      
      // Оценка позиции (если есть GPS)
      updatePositionEstimate(data, dt);
      
      // Оценка скоростей
      updateVelocityEstimate(data, dt);
    }
    
    bool isHealthy() override {
      return sensorModule && sensorModule->isHealthy();
    }
    
    String getName() override { return "StateEstimator"; }
    
  private:
    void updateAttitudeEstimate(const UniversalSensorLib::SensorData& data, float dt) {
      // Интегрирование гироскопа
      currentState.roll += data.gyroX * dt;
      currentState.pitch += data.gyroY * dt;
      currentState.yaw += data.gyroZ * dt;
      
      // Коррекция по акселерометру
      float accelRoll = atan2(data.accelY, data.accelZ) * 180.0 / PI;
      float accelPitch = atan2(-data.accelX, sqrt(data.accelY*data.accelY + data.accelZ*data.accelZ)) * 180.0 / PI;
      
      float alpha = 0.98;
      currentState.roll = alpha * currentState.roll + (1.0 - alpha) * accelRoll;
      currentState.pitch = alpha * currentState.pitch + (1.0 - alpha) * accelPitch;
      
      // Угловые скорости
      currentState.rollRate = data.gyroX;
      currentState.pitchRate = data.gyroY;
      currentState.yawRate = data.gyroZ;
    }
    
    void updatePositionEstimate(const UniversalSensorLib::SensorData& data, float dt) {
      // Высота по барометру
      currentState.z = data.altitude;
      
      // Вертикальная скорость (производная высоты)
      static float lastAltitude = 0;
      currentState.vz = (data.altitude - lastAltitude) / dt;
      lastAltitude = data.altitude;
    }
    
    void updateVelocityEstimate(const UniversalSensorLib::SensorData& data, float dt) {
      // Интегрирование ускорений для получения скоростей
      // (упрощенная реализация)
      currentState.vx += data.accelX * 9.81 * dt;
      currentState.vy += data.accelY * 9.81 * dt;
    }
  };
  
  // Модуль управления
  class ControlModule : public Module {
  public:
    struct ControlInputs {
      float rollCommand, pitchCommand, yawCommand, throttleCommand;
      bool isArmed;
      int flightMode;  // 0=Manual, 1=Stabilize, 2=AltHold, etc.
    } inputs;
    
    struct ControlOutputs {
      float motor1, motor2, motor3, motor4;  // 0.0-1.0
    } outputs;
    
  private:
    StateEstimatorModule* stateEstimator;
    
    // PID контроллеры
    struct PIDController {
      float kp, ki, kd;
      float integral, lastError;
      float outputMin, outputMax;
      
      float update(float setpoint, float measurement, float dt) {
        float error = setpoint - measurement;
        
        integral += error * dt;
        integral = constrain(integral, -10, 10);  // Anti-windup
        
        float derivative = (error - lastError) / dt;
        lastError = error;
        
        float output = kp * error + ki * integral + kd * derivative;
        return constrain(output, outputMin, outputMax);
      }
    };
    
    PIDController rollPID = {1.0, 0.1, 0.05, 0, 0, -1, 1};
    PIDController pitchPID = {1.0, 0.1, 0.05, 0, 0, -1, 1};
    PIDController yawPID = {1.0, 0.1, 0.05, 0, 0, -1, 1};
    PIDController altPID = {0.5, 0.1, 0.2, 0, 0, -0.5, 0.5};
    
  public:
    ControlModule(StateEstimatorModule* estimator) : stateEstimator(estimator) {}
    
    void init() override {
      outputs = {0, 0, 0, 0};
    }
    
    void update(float dt) override {
      if (!stateEstimator || !stateEstimator->isHealthy()) {
        // Аварийное отключение
        outputs = {0, 0, 0, 0};
        return;
      }
      
      if (!inputs.isArmed) {
        outputs = {0, 0, 0, 0};
        return;
      }
      
      switch (inputs.flightMode) {
        case 0: // Manual
          manualControl();
          break;
        case 1: // Stabilize
          stabilizeControl(dt);
          break;
        case 2: // Altitude Hold
          altitudeHoldControl(dt);
          break;
      }
    }
    
    bool isHealthy() override {
      return stateEstimator && stateEstimator->isHealthy();
    }
    
    String getName() override { return "ControlModule"; }
    
  private:
    void manualControl() {
      // Прямое управление
      float base = inputs.throttleCommand;
      
      outputs.motor1 = base + inputs.pitchCommand - inputs.rollCommand - inputs.yawCommand;
      outputs.motor2 = base - inputs.pitchCommand - inputs.rollCommand + inputs.yawCommand;
      outputs.motor3 = base - inputs.pitchCommand + inputs.rollCommand - inputs.yawCommand;
      outputs.motor4 = base + inputs.pitchCommand + inputs.rollCommand + inputs.yawCommand;
      
      constrainOutputs();
    }
    
    void stabilizeControl(float dt) {
      auto& state = stateEstimator->currentState;
      
      // Желаемые углы на основе команд
      float desiredRoll = inputs.rollCommand * 30.0;    // ±30°
      float desiredPitch = inputs.pitchCommand * 30.0;
      float desiredYawRate = inputs.yawCommand * 180.0; // ±180°/s
      
      // PID управление
      float rollOutput = rollPID.update(desiredRoll, state.roll, dt);
      float pitchOutput = pitchPID.update(desiredPitch, state.pitch, dt);
      float yawOutput = yawPID.update(desiredYawRate, state.yawRate, dt);
      
      // Микширование на моторы
      float throttle = inputs.throttleCommand;
      
      outputs.motor1 = throttle + pitchOutput - rollOutput - yawOutput;
      outputs.motor2 = throttle - pitchOutput - rollOutput + yawOutput;
      outputs.motor3 = throttle - pitchOutput + rollOutput - yawOutput;
      outputs.motor4 = throttle + pitchOutput + rollOutput + yawOutput;
      
      constrainOutputs();
    }
    
    void altitudeHoldControl(float dt) {
      // Сначала стабилизация углов
      stabilizeControl(dt);
      
      // Затем управление высотой
      static float targetAltitude = 0;
      static bool altitudeSet = false;
      
      if (!altitudeSet) {
        targetAltitude = stateEstimator->currentState.z;
        altitudeSet = true;
      }
      
      // Изменение целевой высоты джойстиком
      if (abs(inputs.throttleCommand - 0.5) > 0.1) {
        targetAltitude += (inputs.throttleCommand - 0.5) * 2.0 * dt;
        altitudeSet = false;
      }
      
      float altOutput = altPID.update(targetAltitude, stateEstimator->currentState.z, dt);
      
      // Добавляем коррекцию высоты ко всем моторам
      outputs.motor1 += altOutput;
      outputs.motor2 += altOutput;
      outputs.motor3 += altOutput;
      outputs.motor4 += altOutput;
      
      constrainOutputs();
    }
    
    void constrainOutputs() {
      outputs.motor1 = constrain(outputs.motor1, 0.0, 1.0);
      outputs.motor2 = constrain(outputs.motor2, 0.0, 1.0);
      outputs.motor3 = constrain(outputs.motor3, 0.0, 1.0);
      outputs.motor4 = constrain(outputs.motor4, 0.0, 1.0);
    }
  };
  
  // Модуль вывода на моторы
  class MotorModule : public Module {
  private:
    ControlModule* controlModule;
    
  public:
    MotorModule(ControlModule* control) : controlModule(control) {}
    
    void init() override {
      // Инициализация PWM каналов для 4 моторов
      ledcSetup(0, 50, 16);  // Motor 1
      ledcSetup(1, 50, 16);  // Motor 2
      ledcSetup(2, 50, 16);  // Motor 3
      ledcSetup(3, 50, 16);  // Motor 4
      
      ledcAttachPin(18, 0);  // Motor 1 pin
      ledcAttachPin(19, 1);  // Motor 2 pin
      ledcAttachPin(21, 2);  // Motor 3 pin
      ledcAttachPin(22, 3);  // Motor 4 pin
    }
    
    void update(float dt) override {
      if (!controlModule || !controlModule->isHealthy()) {
        // Аварийная остановка
        ledcWrite(0, 3277);  // 1ms pulse (stop)
        ledcWrite(1, 3277);
        ledcWrite(2, 3277);
        ledcWrite(3, 3277);
        return;
      }
      
      auto& outputs = controlModule->outputs;
      
      // Преобразование 0.0-1.0 в PWM значения 1000-2000us
      // При 50Hz и 16-bit: 1ms = 3277, 2ms = 6554
      uint16_t pwm1 = map(outputs.motor1 * 1000, 0, 1000, 3277, 6554);
      uint16_t pwm2 = map(outputs.motor2 * 1000, 0, 1000, 3277, 6554);
      uint16_t pwm3 = map(outputs.motor3 * 1000, 0, 1000, 3277, 6554);
      uint16_t pwm4 = map(outputs.motor4 * 1000, 0, 1000, 3277, 6554);
      
      ledcWrite(0, pwm1);
      ledcWrite(1, pwm2);
      ledcWrite(2, pwm3);
      ledcWrite(3, pwm4);
    }
    
    bool isHealthy() override {
      return controlModule && controlModule->isHealthy();
    }
    
    String getName() override { return "MotorModule"; }
  };
  
private:
  std::vector<Module*> modules;
  unsigned long lastUpdate;
  
public:
  void addModule(Module* module) {
    modules.push_back(module);
  }
  
  void init() {
    Serial.println("Initializing Autopilot Framework...");
    
    for (Module* module : modules) {
      Serial.printf("Initializing %s...", module->getName().c_str());
      module->init();
      Serial.println(" OK");
    }
    
    lastUpdate = millis();
    Serial.println("Autopilot Framework ready!");
  }
  
  void update() {
    unsigned long currentTime = millis();
    float dt = (currentTime - lastUpdate) / 1000.0;
    lastUpdate = currentTime;
    
    // Обновление всех модулей
    for (Module* module : modules) {
      module->update(dt);
    }
    
    // Проверка здоровья системы
    checkSystemHealth();
  }
  
private:
  void checkSystemHealth() {
    static unsigned long lastHealthCheck = 0;
    
    if (millis() - lastHealthCheck > 1000) {  // Каждую секунду
      bool systemHealthy = true;
      
      for (Module* module : modules) {
        if (!module->isHealthy()) {
          Serial.printf("WARNING: %s is not healthy!\n", module->getName().c_str());
          systemHealthy = false;
        }
      }
      
      if (!systemHealthy) {
        Serial.println("SYSTEM HEALTH CHECK FAILED!");
        // Здесь можно добавить аварийные процедуры
      }
      
      lastHealthCheck = millis();
    }
  }
};

// Пример использования фреймворка
AutopilotFramework autopilot;
AutopilotFramework::SensorModule sensorModule;
AutopilotFramework::StateEstimatorModule stateEstimator(&sensorModule);
AutopilotFramework::ControlModule controlModule(&stateEstimator);
AutopilotFramework::MotorModule motorModule(&controlModule);

void setup() {
  Serial.begin(115200);
  Wire.begin();
  
  // Добавляем модули в фреймворк
  autopilot.addModule(&sensorModule);
  autopilot.addModule(&stateEstimator);
  autopilot.addModule(&controlModule);
  autopilot.addModule(&motorModule);
  
  // Инициализация
  autopilot.init();
  
  // Настройка управления
  controlModule.inputs.flightMode = 1;  // Stabilize mode
  controlModule.inputs.isArmed = false;
}

void loop() {
  autopilot.update();
  
  // Обработка команд с радио
  // controlModule.inputs.rollCommand = ...
  // controlModule.