Skip to main content

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

⚡ МОДУЛЬ 5: “ESP32 И ЭЛЕКТРОНИКА” (8 часов)

“От железа к интеллекту: программируем мозг дрона”


🧠 ФИЛОСОФИЯ ЦИФРОВОГО УПРАВЛЕНИЯ

Переход от механики к цифре:

🔄 ЭВОЛЮЦИЯ ПОНИМАНИЯ СИСТЕМЫ:

УРОВЕНЬ "МЕХАНИК" (Модуль 4):
- Дрон как механическая система
- Понимание физических принципов
- Настройка готовых компонентов
- Диагностика "железных" проблем

УРОВЕНЬ "ПРОГРАММИСТ" (Модуль 5):
- Дрон как программируемая система
- Контроль каждого алгоритма
- Создание собственной логики
- Диагностика программных ошибок

УРОВЕНЬ "АРХИТЕКТОР" (Модуль 6):
- Дрон как интеллектуальная система
- Проектирование поведения
- Интеграция множественных датчиков
- Создание автономного интеллекта

ESP32: почему именно этот микроконтроллер?

🎯 ПРЕИМУЩЕСТВА ESP32 ДЛЯ ДРОНОВ:

ВЫЧИСЛИТЕЛЬНАЯ МОЩНОСТЬ:
- Dual-core 240MHz процессор
- 520KB RAM + 4MB Flash
- Поддержка floating-point операций
- Достаточно для real-time управления

ВСТРОЕННАЯ СВЯЗЬ:
- WiFi 802.11 b/g/n
- Bluetooth Classic + BLE
- Возможность создания mesh сетей
- OTA (Over-The-Air) обновления

БОГАТАЯ ПЕРИФЕРИЯ:
- 18 каналов ADC (аналого-цифровое преобразование)
- 2 канала DAC (цифро-аналоговое преобразование)
- PWM на любом пине
- SPI, I2C, UART интерфейсы
- Touch sensors, Hall sensor

ЭКОСИСТЕМА РАЗРАБОТКИ:
- Arduino IDE совместимость
- ESP-IDF для профессиональной разработки
- PlatformIO для продвинутых проектов
- Огромное сообщество разработчиков

ЭКОНОМИЧНОСТЬ:
- Цена $3-10 за чип
- Низкое энергопотребление
- Встроенный WiFi = экономия на отдельных модулях
- Open Source экосистема

🔌 УРОК 1: ОСНОВЫ ЭЛЕКТРОНИКИ ДЛЯ ДРОНОВ (2 часа)

1.1 Электричество: от теории к практике

ПРАКТИЧЕСКИЙ КУРС “ЭЛЕКТРИЧЕСТВО БЕЗ ФОРМУЛ”:

⚡ БАЗОВЫЕ ПОНЯТИЯ ЧЕРЕЗ АНАЛОГИИ:

НАПРЯЖЕНИЕ (Voltage) = ДАВЛЕНИЕ ВОДЫ:
Представь водопровод:
- Высокое давление → быстрый поток
- Низкое давление → медленный поток
- 12V батарея = "высокое давление" для электронов
- 3.3V логика = "низкое давление" для микросхем

Практический эксперимент:
1. Измерить напряжение батареи мультиметром
2. Подключить светодиод к 12V → горит ярко
3. Подключить тот же LED к 3.3V → горит тускло
4. Вывод: напряжение определяет "силу" воздействия

ТОК (Current) = КОЛИЧЕСТВО ВОДЫ:
Аналогия с рекой:
- Широкая река = большой ток
- Ручеек = маленький ток
- Мотор дрона потребляет 20A = "широкая река"
- LED потребляет 20mA = "тонкий ручеек"

Практическое измерение:
1. Подключить амперметр последовательно с нагрузкой
2. Измерить ток потребления мотора на холостом ходу
3. Дать газу → ток увеличился в разы
4. Вывод: ток зависит от нагрузки

СОПРОТИВЛЕНИЕ (Resistance) = ПРЕПЯТСТВИЕ:
Как камни в реке замедляют поток:
- Резистор = "камень" для электронов
- Большое сопротивление = маленький ток
- Маленькое сопротивление = большой ток

Эксперимент с резисторами:
1. LED + резистор 1кОм → тускло светится
2. LED + резистор 100Ом → ярко светится  
3. LED без резистора → сгорает!
4. Вывод: резисторы ограничивают ток

ЗАКОН ОМА В ДЕЙСТВИИ:

🔍 ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ U = I × R:

ЗАДАЧА 1: Подобрать резистор для LED
Дано:
- Напряжение питания: 5V
- LED падение напряжения: 2V
- Нужный ток через LED: 20mA

Решение без формул:
1. На резисторе должно "потеряться": 5V - 2V = 3V
2. Через резистор должно пройти: 20mA
3. По таблице или калькулятору: R = 3V / 0.02A = 150 Ом
4. Выбираем ближайший стандартный: 150 Ом

ЗАДАЧА 2: Рассчитать потребление мотора
Дано:
- Напряжение батареи: 11.1V (3S LiPo)
- Сопротивление мотора: 0.1 Ом
- Ток заторможенного ротора: I = U/R = 11.1/0.1 = 111A!

Вывод: Никогда не блокировать вращение мотора!

ЗАДАЧА 3: Выбрать сечение проводов
- Ток потребления: 60A
- Допустимая плотность тока: 5A/мм²
- Нужное сечение: 60A / 5A/мм² = 12 мм²
- Диаметр провода: ~4мм

Практический выбор: провод 10 AWG (5.26 мм²) с запасом

1.2 Компоненты электронной системы дрона

АНАТОМИЯ ЭЛЕКТРОНИКИ ДРОНА:

🔧 ОСНОВНЫЕ КОМПОНЕНТЫ:

1. ИСТОЧНИКИ ПИТАНИЯ:
LiPo батарея (11.1V - 22.2V):
- Основное питание моторов
- Высокий ток разряда (50C+)
- Нестабильное напряжение (зависит от заряда)

BEC (Battery Eliminator Circuit):
- Преобразование 12V → 5V для сервоприводов
- Стабилизированное напряжение
- Обычно встроен в ESC

LDO стабилизатор (5V → 3.3V):
- Питание микроконтроллеров
- Низкий ток, высокая стабильность
- Встроен в большинство плат

2. СИЛОВАЯ ЭЛЕКТРОНИКА:
ESC (Electronic Speed Controller):
- Преобразование DC → 3-phase AC для моторов
- PWM управление скоростью
- Встроенная защита от перегрузки

Power Distribution Board (PDB):
- Распределение питания на 4 ESC
- Встроенные фильтры помех
- Места для дополнительных модулей

3. УПРАВЛЯЮЩАЯ ЭЛЕКТРОНИКА:
Flight Controller:
- Основной "мозг" дрона
- Обработка данных датчиков
- Генерация управляющих сигналов

Радиоприемник:
- Прием команд с пульта
- Декодирование PPM/SBUS сигналов
- Failsafe при потере связи

4. ДАТЧИКИ:
IMU (Inertial Measurement Unit):
- Гироскопы + акселерометры
- Определение ориентации в пространстве
- Частота обновления 1000+ Гц

Barometer:
- Измерение атмосферного давления
- Определение высоты
- Стабилизация по высоте

GPS:
- Позиционирование
- Навигация
- Return-to-Home функция

ПРАКТИЧЕСКАЯ СХЕМОТЕХНИКА:

📐 СХЕМЫ ПОДКЛЮЧЕНИЯ:

СХЕМА ПИТАНИЯ:

LiPo 3S (11.1V) │ ├── PDB ──┬── ESC1 ── Motor1 │ ├── ESC2 ── Motor2
│ ├── ESC3 ── Motor3 │ └── ESC4 ── Motor4 │ ├── BEC (5V) ──┬── Servo1 │ ├── Servo2 │ └── Receiver Power │ └── LDO (3.3V) ──┬── Flight Controller ├── GPS Module └── Sensors


СХЕМА УПРАВЛЕНИЯ:

Transmitter ~~~ Receiver ──── Flight Controller ────┬── ESC1 │ ├── ESC2 │ ├── ESC3 ┌─────────────┼──────────────└── ESC4 │ │ ┌── IMU ┌── GPS ├── Baro ├── Compass
├── Sonar └── Optical Flow └── Cameras


ЗАЗЕМЛЕНИЕ И ЭКРАНИРОВАНИЕ:
- Общий "земляной" провод для всех компонентов
- Экранирование силовых проводов от сигнальных
- Развязка аналоговой и цифровой "земли"
- Ферритовые кольца на длинных проводах

💻 УРОК 2: ПРОГРАММИРОВАНИЕ ESP32 (3 часа)

2.1 Настройка среды разработки

STEP-BY-STEP SETUP:

🛠️ УСТАНОВКА ARDUINO IDE + ESP32:

ШАГ 1: Установка Arduino IDE
1. Скачать с official site: arduino.cc
2. Установить стандартным способом
3. Запустить и проверить работу

ШАГ 2: Добавление ESP32 Support
1. File → Preferences
2. В "Additional Boards Manager URLs" добавить:
   https://dl.espressif.com/dl/package_esp32_index.json
3. Tools → Board → Boards Manager
4. Найти "esp32" и установить

ШАГ 3: Выбор платы
1. Tools → Board → ESP32 Arduino
2. Выбрать "ESP32 Dev Module"
3. Настроить параметры:
   - Flash Mode: QIO
   - Flash Size: 4MB
   - Upload Speed: 921600

ШАГ 4: Тест подключения
```cpp
void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 Test Start!");
}

void loop() {
  Serial.println("Hello from ESP32!");
  delay(1000);
}

Загрузить код и проверить вывод в Serial Monitor


**АЛЬТЕРНАТИВНЫЕ СРЕДЫ РАЗРАБОТКИ:**

🚀 ПРОДВИНУТЫЕ ИНСТРУМЕНТЫ:

PLATFORMIO (рекомендуется для серьезной разработки): Преимущества:

  • Встроенный менеджер библиотек
  • Поддержка множества плат
  • Интеграция с VS Code
  • Профессиональный debugger

Установка:

  1. Установить VS Code
  2. Установить расширение PlatformIO
  3. Создать новый проект для ESP32
  4. Автоматическая установка toolchain

ESP-IDF (для профессиональной разработки):

  • Native ESP32 framework от Espressif
  • Полный доступ к всем возможностям
  • Real-time операционная система (FreeRTOS)
  • Профессиональные инструменты отладки

MICROPYTHON (для быстрого прототипирования):

  • Интерпретируемый Python на ESP32
  • Интерактивная разработка через REPL
  • Быстрое тестирование идей
  • Ограниченная производительность

### **2.2 Основы программирования ESP32**

**СТРУКТУРА ПРОГРАММЫ:**
```cpp
// =====================================
// БАЗОВАЯ СТРУКТУРА ESP32 ПРОГРАММЫ
// =====================================

// 1. ПОДКЛЮЧЕНИЕ БИБЛИОТЕК
#include <WiFi.h>          // WiFi функции
#include <Wire.h>          // I2C протокол
#include <SPI.h>           // SPI протокол

// 2. ОПРЕДЕЛЕНИЕ КОНСТАНТ
#define LED_PIN 2          // Встроенный LED
#define MOTOR_PIN 18       // PWM для мотора
#define SENSOR_PIN 34      // Аналоговый вход

// 3. ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
int sensorValue = 0;
float motorSpeed = 0.0;
bool systemArmed = false;

// 4. ФУНКЦИЯ ИНИЦИАЛИЗАЦИИ (выполняется один раз)
void setup() {
  // Инициализация Serial для отладки
  Serial.begin(115200);
  Serial.println("=== ESP32 DRONE CONTROLLER ===");
  
  // Настройка пинов
  pinMode(LED_PIN, OUTPUT);
  pinMode(MOTOR_PIN, OUTPUT);
  pinMode(SENSOR_PIN, INPUT);
  
  // Инициализация PWM для моторов
  ledcSetup(0, 50, 16);      // Канал 0, 50Hz, 16-bit разрешение
  ledcAttachPin(MOTOR_PIN, 0);
  
  // Инициализация других систем
  initWiFi();
  initSensors();
  
  Serial.println("Setup complete!");
}

// 5. ГЛАВНЫЙ ЦИКЛ (выполняется бесконечно)
void loop() {
  // Чтение датчиков
  readSensors();
  
  // Обработка команд
  processCommands();
  
  // Управление моторами
  controlMotors();
  
  // Отправка телеметрии
  sendTelemetry();
  
  // Небольшая задержка для стабильности
  delay(20);  // 50Hz основной цикл
}

// =====================================
// ПОЛЬЗОВАТЕЛЬСКИЕ ФУНКЦИИ
// =====================================

void initWiFi() {
  WiFi.begin("YourWiFi", "YourPassword");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("WiFi Connected!");
}

void initSensors() {
  Wire.begin();  // Инициализация I2C
  // Здесь инициализация IMU, барометра и т.д.
}

void readSensors() {
  sensorValue = analogRead(SENSOR_PIN);
  // Чтение других датчиков
}

void processCommands() {
  // Обработка команд с пульта или WiFi
}

void controlMotors() {
  if (systemArmed) {
    // Преобразование команд в PWM сигналы
    int pwmValue = map(motorSpeed, 0, 100, 1000, 2000);
    ledcWrite(0, pwmValue);
  } else {
    ledcWrite(0, 1000);  // Моторы остановлены
  }
}

void sendTelemetry() {
  // Отправка данных через WiFi или Serial
  Serial.print("Sensor: ");
  Serial.print(sensorValue);
  Serial.print(" Motor: ");
  Serial.println(motorSpeed);
}

РАБОТА С ПОРТАМИ ВВОДА-ВЫВОДА:

// =====================================
// GPIO (GENERAL PURPOSE INPUT/OUTPUT)
// =====================================

// ЦИФРОВЫЕ ВЫХОДЫ (для LED, реле, и т.д.)
void setupDigitalOutputs() {
  pinMode(LED_PIN, OUTPUT);
  
  digitalWrite(LED_PIN, HIGH);  // Включить LED
  delay(1000);
  digitalWrite(LED_PIN, LOW);   // Выключить LED
}

// ЦИФРОВЫЕ ВХОДЫ (для кнопок, концевиков)
void setupDigitalInputs() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // Встроенный pull-up резистор
  
  if (digitalRead(BUTTON_PIN) == LOW) {
    Serial.println("Button pressed!");
  }
}

// АНАЛОГОВЫЕ ВХОДЫ (для датчиков, потенциометров)
void setupAnalogInputs() {
  // ESP32 имеет 12-bit ADC (0-4095)
  int rawValue = analogRead(SENSOR_PIN);
  
  // Преобразование в напряжение (0-3.3V)
  float voltage = rawValue * (3.3 / 4095.0);
  
  // Преобразование в физическую величину
  float temperature = (voltage - 0.5) * 100;  // Для TMP36
  
  Serial.print("Raw: "); Serial.print(rawValue);
  Serial.print(" Voltage: "); Serial.print(voltage);
  Serial.print(" Temp: "); Serial.println(temperature);
}

// PWM ВЫХОДЫ (для моторов, сервоприводов)
void setupPWMOutputs() {
  // Настройка PWM канала
  ledcSetup(0, 50, 16);        // Канал 0, 50Hz, 16-bit
  ledcAttachPin(MOTOR_PIN, 0); // Привязать пин к каналу
  
  // Генерация PWM сигнала
  // Для ESC: 1000-2000 микросекунд (1ms-2ms)
  // При 50Hz и 16-bit: 1ms = 3277, 2ms = 6554
  
  ledcWrite(0, 3277);   // Минимальный газ
  delay(1000);
  ledcWrite(0, 4915);   // Средний газ (1.5ms)
  delay(1000);
  ledcWrite(0, 6554);   // Максимальный газ
}

2.3 Протоколы связи

I2C ДЛЯ ДАТЧИКОВ:

// =====================================
// I2C ПРОТОКОЛ (для IMU, барометра, компаса)
// =====================================

#include <Wire.h>

// Пример работы с MPU6050 (гироскоп + акселерометр)
#define MPU6050_ADDR 0x68

void setupIMU() {
  Wire.begin();
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // Включить датчик
  Wire.endTransmission(true);
  
  Serial.println("MPU6050 initialized");
}

struct IMUData {
  float gyroX, gyroY, gyroZ;
  float accelX, accelY, accelZ;
};

IMUData readIMU() {
  IMUData data;
  
  // Запрос данных
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);  // Начальный регистр данных
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 14, true);
  
  // Чтение сырых данных (16-bit значения)
  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 / 16384.0;  // ±2g range
  data.accelY = rawAccelY / 16384.0;
  data.accelZ = rawAccelZ / 16384.0;
  
  data.gyroX = rawGyroX / 131.0;      // ±250°/s range
  data.gyroY = rawGyroY / 131.0;
  data.gyroZ = rawGyroZ / 131.0;
  
  return data;
}

SPI ДЛЯ ВЫСОКОСКОРОСТНЫХ ДАТЧИКОВ:

// =====================================
// SPI ПРОТОКОЛ (для быстрых IMU, SD карт)
// =====================================

#include <SPI.h>

#define CS_PIN 5    // Chip Select
#define SCLK_PIN 18 // Serial Clock  
#define MISO_PIN 19 // Master In Slave Out
#define MOSI_PIN 23 // Master Out Slave In

void setupSPI() {
  SPI.begin(SCLK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);  // Неактивное состояние
  
  Serial.println("SPI initialized");
}

// Пример чтения регистра через SPI
uint8_t readSPIRegister(uint8_t address) {
  digitalWrite(CS_PIN, LOW);   // Активировать устройство
  
  SPI.transfer(address | 0x80); // Команда чтения (bit 7 = 1)
  uint8_t data = SPI.transfer(0x00); // Читаем данные
  
  digitalWrite(CS_PIN, HIGH);  // Деактивировать
  
  return data;
}

// Пример записи в регистр через SPI
void writeSPIRegister(uint8_t address, uint8_t data) {
  digitalWrite(CS_PIN, LOW);
  
  SPI.transfer(address & 0x7F); // Команда записи (bit 7 = 0)
  SPI.transfer(data);
  
  digitalWrite(CS_PIN, HIGH);
}

UART ДЛЯ GPS И ТЕЛЕМЕТРИИ:

// =====================================
// UART ПРОТОКОЛ (для GPS, радио модулей)
// =====================================

// ESP32 имеет 3 UART порта
HardwareSerial SerialGPS(1);  // UART1 для GPS
HardwareSerial SerialRadio(2); // UART2 для радио

void setupUART() {
  // GPS обычно работает на 9600 baud
  SerialGPS.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17
  
  // Радио модуль на высокой скорости
  SerialRadio.begin(115200, SERIAL_8N1, 14, 15); // RX=14, TX=15
  
  Serial.println("UART initialized");
}

// Простой парсер NMEA для GPS
void parseGPS() {
  if (SerialGPS.available()) {
    String nmeaString = SerialGPS.readStringUntil('\n');
    
    if (nmeaString.startsWith("$GPGGA")) {
      // Парсинг координат из GPGGA строки
      // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
      
      int comma1 = nmeaString.indexOf(',', 7);   // После времени
      int comma2 = nmeaString.indexOf(',', comma1 + 1);
      int comma3 = nmeaString.indexOf(',', comma2 + 1);
      int comma4 = nmeaString.indexOf(',', comma3 + 1);
      int comma5 = nmeaString.indexOf(',', comma4 + 1);
      
      String latStr = nmeaString.substring(comma1 + 1, comma2);
      String latDir = nmeaString.substring(comma2 + 1, comma3);
      String lonStr = nmeaString.substring(comma3 + 1, comma4);
      String lonDir = nmeaString.substring(comma4 + 1, comma5);
      
      if (latStr.length() > 0 && lonStr.length() > 0) {
        float latitude = parseCoordinate(latStr, latDir);
        float longitude = parseCoordinate(lonStr, lonDir);
        
        Serial.print("GPS: ");
        Serial.print(latitude, 6);
        Serial.print(", ");
        Serial.println(longitude, 6);
      }
    }
  }
}

float parseCoordinate(String coord, String direction) {
  // Конвертация DDMM.MMMM в десятичные градусы
  float degrees = coord.substring(0, 2).toFloat();
  float minutes = coord.substring(2).toFloat();
  
  float decimal = degrees + minutes / 60.0;
  
  if (direction == "S" || direction == "W") {
    decimal = -decimal;
  }
  
  return decimal;
}

🎛️ УРОК 3: СОЗДАНИЕ СОБСТВЕННОГО АВТОПИЛОТА (3 часа)

3.1 Архитектура системы управления

КОНЦЕПТУАЛЬНАЯ СХЕМА:

🏗️ СТРУКТУРА АВТОПИЛОТА:

INPUT LAYER (Входные данные):
├── Radio Receiver (команды пилота)
├── IMU (ориентация в пространстве)  
├── GPS (позиция)
├── Barometer (высота)
├── Optical Flow (скорость относительно земли)
└── Rangefinder (точная высота)

PROCESSING LAYER (Обработка):
├── Sensor Fusion (объединение данных датчиков)
├── State Estimation (оценка состояния дрона)
├── Control Algorithms (алгоритмы управления)
├── Mission Planning (планирование полета)
└── Safety Monitoring (контроль безопасности)

OUTPUT LAYER (Выходные сигналы):
├── Motor 1 PWM
├── Motor 2 PWM  
├── Motor 3 PWM
├── Motor 4 PWM
├── Servo Outputs (для камеры, сброса груза)
└── Status LEDs

COMMUNICATION LAYER (Связь):
├── Telemetry Radio (данные на землю)
├── WiFi (настройка и отладка)
└── Bluetooth (мобильные приложения)

БАЗОВАЯ РЕАЛИЗАЦИЯ:

// =====================================
// ПРОСТОЙ АВТОПИЛОТ НА ESP32
// =====================================

#include <Wire.h>
#include <WiFi.h>

// ===== СТРУКТУРЫ ДАННЫХ =====
struct Vector3 {
  float x, y, z;
};

struct Attitude {
  float roll, pitch, yaw;    // Углы в градусах
};

struct Position {
  float latitude, longitude, altitude;
};

struct ControlInputs {
  float throttle;  // 0.0 - 1.0
  float roll;      // -1.0 - 1.0
  float pitch;     // -1.0 - 1.0  
  float yaw;       // -1.0 - 1.0
};

struct MotorOutputs {
  float motor1, motor2, motor3, motor4;  // 0.0 - 1.0
};

// ===== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ =====
Vector3 gyro, accel, mag;
Attitude attitude;
Position position;
ControlInputs controls;
MotorOutputs motors;

// Настройки PID регуляторов
float rollKp = 1.0, rollKi = 0.1, rollKd = 0.05;
float pitchKp = 1.0, pitchKi = 0.1, pitchKd = 0.05;
float yawKp = 1.0, yawKi = 0.1, yawKd = 0.05;

// Переменные для PID
float rollError, rollErrorPrev, rollErrorSum;
float pitchError, pitchErrorPrev, pitchErrorSum;
float yawError, yawErrorPrev, yawErrorSum;

unsigned long lastTime = 0;

// ===== ОСНОВНОЙ КОД =====
void setup() {
  Serial.begin(115200);
  Serial.println("=== ESP32 AUTOPILOT v1.0 ===");
  
  initHardware();
  initSensors();
  initMotors();
  
  Serial.println("Autopilot ready!");
}

void loop() {
  unsigned long currentTime = millis();
  float dt = (currentTime - lastTime) / 1000.0;  // Время в секундах
  lastTime = currentTime;
  
  // 1. Чтение датчиков
  readSensors();
  
  // 2. Оценка состояния
  updateAttitude(dt);
  
  // 3. Чтение команд пилота
  readControlInputs();
  
  // 4. Алгоритмы управления
  calculatePIDControl(dt);
  
  // 5. Управление моторами
  updateMotors();
  
  // 6. Отправка телеметрии
  sendTelemetry();
  
  delay(20);  // 50Hz основной цикл
}

3.2 Алгоритмы стабилизации

PID КОНТРОЛЛЕР:

// =====================================
// PID РЕГУЛЯТОР ДЛЯ СТАБИЛИЗАЦИИ
// =====================================

void calculatePIDControl(float dt) {
  // Желаемые углы на основе команд пилота
  float desiredRoll = controls.roll * 30.0;   // Максимум ±30°
  float desiredPitch = controls.pitch * 30.0;
  float desiredYawRate = controls.yaw * 180.0; // Максимум ±180°/с
  
  // === ROLL PID ===
  rollError = desiredRoll - attitude.roll;
  rollErrorSum += rollError * dt;
  rollErrorSum = constrain(rollErrorSum, -10, 10);  // Anti-windup
  
  float rollDerivative = (rollError - rollErrorPrev) / dt;
  rollErrorPrev = rollError;
  
  float rollOutput = rollKp * rollError + 
                     rollKi * rollErrorSum + 
                     rollKd * rollDerivative;
  
  // === PITCH PID ===
  pitchError = desiredPitch - attitude.pitch;
  pitchErrorSum += pitchError * dt;
  pitchErrorSum = constrain(pitchErrorSum, -10, 10);
  
  float pitchDerivative = (pitchError - pitchErrorPrev) / dt;
  pitchErrorPrev = pitchError;
  
  float pitchOutput = pitchKp * pitchError + 
                      pitchKi * pitchErrorSum + 
                      pitchKd * pitchDerivative;
  
  // === YAW PID (на угловой скорости) ===
  yawError = desiredYawRate - gyro.z;
  yawErrorSum += yawError * dt;
  yawErrorSum = constrain(yawErrorSum, -10, 10);
  
  float yawDerivative = (yawError - yawErrorPrev) / dt;
  yawErrorPrev = yawError;
  
  float yawOutput = yawKp * yawError + 
                    yawKi * yawErrorSum + 
                    yawKd * yawDerivative;
  
  // === МИКШИРОВАНИЕ НА МОТОРЫ ===
  // Квадрокоптер X конфигурация:
  // Motor1 (передний правый): +pitch, -roll, -yaw
  // Motor2 (задний правый):   -pitch, -roll, +yaw  
  // Motor3 (задний левый):    -pitch, +roll, -yaw
  // Motor4 (передний левый):  +pitch, +roll, +yaw
  
  float throttle = controls.throttle;
  
  motors.motor1 = throttle + pitchOutput - rollOutput - yawOutput;
  motors.motor2 = throttle - pitchOutput - rollOutput + yawOutput;
  motors.motor3 = throttle - pitchOutput + rollOutput - yawOutput;
  motors.motor4 = throttle + pitchOutput + rollOutput + yawOutput;
  
  // Ограничение выходов 0.0-1.0
  motors.motor1 = constrain(motors.motor1, 0.0, 1.0);
  motors.motor2 = constrain(motors.motor2, 0.0, 1.0);
  motors.motor3 = constrain(motors.motor3, 0.0, 1.0);
  motors.motor4 = constrain(motors.motor4, 0.0, 1.0);
}

// Функция ограничения значений
float constrain(float value, float min_val, float max_val) {
  if (value < min_val) return min_val;
  if (value > max_val) return max_val;
  return value;
}

ОЦЕНКА ОРИЕНТАЦИИ:

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

void updateAttitude(float dt) {
  // Интегрирование гироскопа (быстро, но дрейфует)
  attitude.roll += gyro.x * dt;
  attitude.pitch += gyro.y * dt;
  attitude.yaw += gyro.z * dt;
  
  // Расчет углов из акселерометра (медленно, но точно)
  float accelRoll = atan2(accel.y, accel.z) * 180.0 / PI;
  float accelPitch = atan2(-accel.x, sqrt(accel.y*accel.y + accel.z*accel.z)) * 180.0 / PI;
  
  // Комплементарный фильтр (объединяем показания)
  float alpha = 0.98;  // Коэффициент доверия гироскопу
  
  attitude.roll = alpha * attitude.roll + (1.0 - alpha) * accelRoll;
  attitude.pitch = alpha * attitude.pitch + (1.0 - alpha) * accelPitch;
  
  // Yaw можно корректировать по магнитометру
  if (mag.x != 0 || mag.y != 0) {
    float magYaw = atan2(mag.y, mag.x) * 180.0 / PI;
    attitude.yaw = alpha * attitude.yaw + (1.0 - alpha) * magYaw;
  }
  
  // Ограничение углов в диапазоне ±180°
  if (attitude.roll > 180) attitude.roll -= 360;
  if (attitude.roll < -180) attitude.roll += 360;
  if (attitude.pitch > 180) attitude.pitch -= 360;
  if (attitude.pitch < -180) attitude.pitch += 360;
  if (attitude.yaw > 180) attitude.yaw -= 360;
  if (attitude.yaw < -180) attitude.yaw += 360;
}

3.3 Режимы полета

РЕАЛИЗАЦИЯ РЕЖИМОВ:

// =====================================
// РЕЖИМЫ ПОЛЕТА
// =====================================

enum FlightMode {
  MANUAL,      // Ручное управление
  STABILIZE,   // Стабилизация углов
  ALT_HOLD,    // Удержание высоты
  LOITER,      // Зависание в точке
  AUTO,        // Автоматический полет
  RTL          // Возврат домой
};

FlightMode currentMode = STABILIZE;
Position homePosition;
bool isArmed = false;

void processFlightMode() {
  switch (currentMode) {
    case MANUAL:
      // Прямое управление моторами
      manualMode();
      break;
      
    case STABILIZE:
      // Стабилизация углов (уже реализована выше)
      stabilizeMode();
      break;
      
    case ALT_HOLD:
      // Стабилизация + удержание высоты
      altHoldMode();
      break;
      
    case LOITER:
      // Стабилизация + удержание позиции
      loiterMode();
      break;
      
    case AUTO:
      // Автоматический полет по waypoints
      autoMode();
      break;
      
    case RTL:
      // Возврат домой
      returnToLaunchMode();
      break;
  }
}

void manualMode() {
  // Прямое преобразование команд в PWM
  motors.motor1 = controls.throttle;
  motors.motor2 = controls.throttle;
  motors.motor3 = controls.throttle;
  motors.motor4 = controls.throttle;
  
  // Добавляем управление по осям
  motors.motor1 += controls.pitch - controls.roll - controls.yaw;
  motors.motor2 += -controls.pitch - controls.roll + controls.yaw;
  motors.motor3 += -controls.pitch + controls.roll - controls.yaw;
  motors.motor4 += controls.pitch + controls.roll + controls.yaw;
  
  // Ограничиваем
  motors.motor1 = constrain(motors.motor1, 0.0, 1.0);
  motors.motor2 = constrain(motors.motor2, 0.0, 1.0);
  motors.motor3 = constrain(motors.motor3, 0.0, 1.0);
  motors.motor4 = constrain(motors.motor4, 0.0, 1.0);
}

void stabilizeMode() {
  // Уже реализована в calculatePIDControl()
  calculatePIDControl(0.02);  // 50Hz
}

void altHoldMode() {
  // Сначала стабилизация углов
  stabilizeMode();
  
  // Затем контроль высоты
  static float targetAltitude = position.altitude;
  static bool altitudeSet = false;
  
  // Установка целевой высоты при первом входе в режим
  if (!altitudeSet) {
    targetAltitude = position.altitude;
    altitudeSet = true;
  }
  
  // Изменение целевой высоты стиком газа
  if (abs(controls.throttle - 0.5) > 0.1) {
    targetAltitude += (controls.throttle - 0.5) * 2.0;  // 2 м/с максимум
    altitudeSet = false;
  }
  
  // PID регулятор высоты
  static float altError, altErrorPrev, altErrorSum;
  float altKp = 0.5, altKi = 0.1, altKd = 0.2;
  
  altError = targetAltitude - position.altitude;
  altErrorSum += altError * 0.02;
  altErrorSum = constrain(altErrorSum, -5, 5);
  
  float altDerivative = (altError - altErrorPrev) / 0.02;
  altErrorPrev = altError;
  
  float altOutput = altKp * altError + altKi * altErrorSum + altKd * altDerivative;
  
  // Модификация throttle для всех моторов
  motors.motor1 += altOutput;
  motors.motor2 += altOutput;
  motors.motor3 += altOutput;
  motors.motor4 += altOutput;
  
  // Ограничение
  motors.motor1 = constrain(motors.motor1, 0.0, 1.0);
  motors.motor2 = constrain(motors.motor2, 0.0, 1.0);
  motors.motor3 = constrain(motors.motor3, 0.0, 1.0);
  motors.motor4 = constrain(motors.motor4, 0.0, 1.0);
}

void returnToLaunchMode() {
  // Простая реализация RTL
  
  // 1. Подняться на безопасную высоту
  float safeAltitude = homePosition.altitude + 20.0;  // +20 метров
  if (position.altitude < safeAltitude) {
    // Подъем
    controls.throttle = 0.7;
    stabilizeMode();
    return;
  }
  
  // 2. Лететь к точке старта
  float distanceToHome = sqrt(
    pow(position.latitude - homePosition.latitude, 2) +
    pow(position.longitude - homePosition.longitude, 2)
  );
  
  if (distanceToHome > 0.00001) {  // ~1 метр точности GPS
    // Вычисляем направление к дому
    float bearingToHome = atan2(
      homePosition.longitude - position.longitude,
      homePosition.latitude - position.latitude
    );
    
    // Устанавливаем команды для полета домой
    controls.pitch = cos(bearingToHome) * 0.3;  // 30% максимального наклона
    controls.roll = sin(bearingToHome) * 0.3;
    controls.yaw = 0;
    controls.throttle = 0.5;  // Постоянная высота
    
    stabilizeMode();
  } else {
    // 3. Посадка над домом
    controls.throttle = 0.3;  // Медленное снижение
    controls.pitch = 0;
    controls.roll = 0;
    controls.yaw = 0;
    
    stabilizeMode();
    
    // Disarm при касании земли
    if (position.altitude <= homePosition.altitude + 0.5) {
      isArmed = false;
    }
  }
}

🔧 ПРАКТИЧЕСКАЯ АТТЕСТАЦИЯ МОДУЛЯ

Комплексный экзамен “ESP32 разработчик”:

ТЕОРЕТИЧЕСКАЯ ЧАСТЬ (30% оценки):

Блоки вопросов:

1. ОСНОВЫ ЭЛЕКТРОНИКИ (25% теории)
   - Закон Ома и его применение
   - Выбор компонентов по характеристикам
   - Принципы построения схем питания
   - Диагностика электронных проблем

2. ПРОГРАММИРОВАНИЕ ESP32 (40% теории)
   - Структура программы и основные функции
   - Работа с GPIO, PWM, ADC
   - Протоколы связи I2C, SPI, UART
   - Обработка прерываний и многозадачность

3. АЛГОРИТМЫ УПРАВЛЕНИЯ (35% теории)
   - Принципы PID регулирования
   - Sensor fusion и фильтрация
   - Режимы полета и их реализация
   - Системы безопасности

Формат: Письменный тест + практическое программирование
Время: 90 минут + 30 минут практика
Проходной балл: 75%

ПРАКТИЧЕСКАЯ ЧАСТЬ (70% оценки):

Задание 1: “Система сбора данных” (35% практики)

// ЗАДАЧА: Создать систему сбора и передачи данных датчиков

/* ТРЕБОВАНИЯ:
1. Подключить IMU датчик по I2C
2. Подключить GPS по UART
3. Настроить WiFi для передачи данных
4. Создать web-интерфейс для просмотра данных в реальном времени
5. Логирование данных на SD карту

КРИТЕРИИ ОЦЕНКИ:
- Корректность подключения датчиков (25%)
- Качество программного кода (30%)
- Функциональность web-интерфейса (25%)
- Стабильность работы системы (20%)
*/

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

// Ваш код здесь...

Задание 2: “Мини-автопилот” (35% практики)

// ЗАДАЧА: Реализовать базовый автопилот для стабилизации

/* ТРЕБОВАНИЯ:
1. Чтение данных IMU с частотой 100Hz
2. Реализация PID регулятора для стабилизации roll/pitch
3. Генерация PWM сигналов для 4 моторов
4. Режимы: Manual, Stabilize, Disarmed
5. Безопасность: автоматическое отключение при потере сигнала

ТЕСТИРОВАНИЕ:
- Симуляция на стенде с сервоприводами
- Проверка отклика на внешние возмущения
- Анализ качества стабилизации

КРИТЕРИИ ОЦЕНКИ:
- Точность чтения датчиков (20%)
- Корректность PID алгоритма (35%)
- Качество генерации PWM (20%)
- Реализация систем безопасности (25%)
*/

// Ваша реализация автопилота...

Уровни сертификации:

⚡ EMBEDDED SYSTEMS ENGINEER (85-100 баллов):

  • Сертификат “Инженер встроенных систем дронов”
  • Право на разработку коммерческих автопилотов
  • Создание собственных решений с нуля
  • Консультирование по техническим вопросам

💻 DRONE PROGRAMMER (75-84 балла):

  • Сертификат “Программист дронов”
  • Модификация существующих автопилотов
  • Интеграция новых датчиков и функций
  • Техническая поддержка систем

🔧 ELECTRONICS TECHNICIAN (65-74 балла):

  • Сертификат “Техник по электронике дронов”
  • Сборка и настройка электронных систем
  • Диагностика и ремонт
  • Базовое программирование

📋 ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ К МОДУЛЮ 5

Расширенные примеры кода:

1. Продвинутая работа с датчиками:

// Калибровка магнитометра
// Автоматическое определение типа GPS
// Фильтр Калмана для sensor fusion
// Обнаружение отказов датчиков

2. Оптимизация производительности:

// Использование DMA для высокоскоростного чтения
// Многозадачность с FreeRTOS
// Оптимизация вычислений с плавающей точкой
// Профилирование производительности

3. Беспроводная связь:

// MAVLink протокол телеметрии
// Mesh-сети между дронами  
// Прямая связь с GCS
// OTA обновления прошивки

4. Системы безопасности:

// Watchdog таймеры
// Резервирование критических систем
// Детекция аномального поведения
// Emergency recovery процедуры

Философское завершение модуля: “Поздравляю! Теперь вы владеете цифровой душой дрона. Вы можете не только собрать механическую конструкцию, но и вдохнуть в неё жизнь через программный код. ESP32 стал вашим инструментом для создания интеллектуальных летающих систем, способных принимать решения, адаптироваться к условиям и выполнять сложные задачи автономно.”

🎯 Результат модуля: Студент освоил программирование микроконтроллеров для дронов, может создавать собственные автопилоты, интегрировать различные датчики и реализовывать алгоритмы управления полетом.