Проблема твоего текущего кода в том, что он использует **релейный алгоритм** (Bang-Bang control). Робот видит линию и резко поворачивает, теряет её — и резко поворачивает обратно. Из-за этого он движется рывками («зигзагом») и на скорости вылетает с трассы. Чтобы робот ехал плавно, нужно использовать **ПД-регулятор** (Пропорционально-Дифференциальный). 1. **Пропорциональность (P):** Чем сильнее мы ушли с линии, тем сильнее нужно подруливать. 2. **Дифференциал (D):** Если мы слишком быстро возвращаемся к линии, нужно «притормозить» поворот, чтобы не пролететь её мимо. Ниже представлен улучшенный код. Он использует твои функции калибровки, но полностью меняет логику движения в `loop()`. ### Обновленный скетч (ПД-регулятор) ```cpp #include // --- ПИНЫ --- #define LEFT_FORWARD 3 #define LEFT_BACKWARD 5 #define RIGHT_FORWARD 6 #define RIGHT_BACKWARD 11 #define LEFT_SENSOR A0 #define RIGHT_SENSOR A7 // --- НАСТРОЙКИ СКОРОСТИ --- // Базовая скорость (когда робот едет прямо) // PWM значение от 0 до 255. // Твои старые значения были около 70-75. Можно начать с них. #define BASE_SPEED 80 #define MAX_SPEED 180 // --- КОЭФФИЦИЕНТЫ ПИД (Нужно подбирать!) --- // Kp - сила реакции на ошибку. Если робот вялый, увеличивай. Если дергается, уменьшай. // Kd - гашение колебаний. Если робот осциллирует (виляет) вокруг линии, увеличивай. float Kp = 0.15; float Kd = 0.8; // #define CALIBRATION // Раскомментируй для первого запуска чтобы откалибровать struct CalibrationData { uint16_t leftMin, leftMax; // Min = Белый, Max = Черный (обычно) uint16_t rightMin, rightMax; } calib; int lastError = 0; void setup() { Serial.begin(9600); pinMode(LEFT_FORWARD, OUTPUT); pinMode(LEFT_BACKWARD, OUTPUT); pinMode(RIGHT_FORWARD, OUTPUT); pinMode(RIGHT_BACKWARD, OUTPUT); pinMode(LEFT_SENSOR, INPUT); pinMode(RIGHT_SENSOR, INPUT); stopMotors(); #ifdef CALIBRATION calibrateSensors(); EEPROM.put(0, calib); Serial.println("Calibration Saved. Re-upload without #define CALIBRATION"); while(1); // Остановка после калибровки #else EEPROM.get(0, calib); // Проверка на мусор в EEPROM (если калибровка пустая) if (calib.leftMax == 0 || calib.leftMax == 65535) { Serial.println("EEPROM Empty! Please Calibrate."); while(1); } #endif } void loop() { int16_t leftRaw = analogRead(LEFT_SENSOR); int16_t rightRaw = analogRead(RIGHT_SENSOR); // 1. Нормализация значений сенсоров от 0 до 1000 // constrain нужен, чтобы map не выдавал странные числа, если значение вышло за пределы калибровки int leftNorm = map(constrain(leftRaw, calib.leftMin, calib.leftMax), calib.leftMin, calib.leftMax, 0, 1000); int rightNorm = map(constrain(rightRaw, calib.rightMin, calib.rightMax), calib.rightMin, calib.rightMax, 0, 1000); // 2. Расчет ошибки // Если сенсоры показывают больше на черном: // Положительная ошибка -> Линия слева (наехали правым датчиком или ушли влево?) // Давай предположим: мы хотим, чтобы разница была 0. // Error = Left - Right. int error = leftNorm - rightNorm; // 3. Логика "Потеряли линию" (Опционально) // Если оба датчика видят белый (значение близко к 0), значит мы вылетели. // Нужно крутиться в сторону последней ошибки. if (leftNorm < 100 && rightNorm < 100) { if (lastError > 0) { // Линия была слева, крутимся влево setMotorSpeed(-BASE_SPEED, BASE_SPEED); } else { // Линия была справа, крутимся вправо setMotorSpeed(BASE_SPEED, -BASE_SPEED); } return; // Пропускаем PID, пока не найдем линию } // 4. ПД-регулятор int dError = error - lastError; // Изменение ошибки (производная) int correction = (int)(Kp * error + Kd * dError); lastError = error; // Запоминаем ошибку для следующего цикла // 5. Расчет скоростей моторов int leftSpeed = BASE_SPEED - correction; int rightSpeed = BASE_SPEED + correction; // 6. Применение скоростей setMotorSpeed(leftSpeed, rightSpeed); // Для отладки (раскомментируй, чтобы настроить Kp и Kd) // Serial.print(leftNorm); Serial.print("\t"); // Serial.print(rightNorm); Serial.print("\t"); // Serial.print(error); Serial.print("\t"); // Serial.println(correction); } // Функция управления моторами с поддержкой отрицательной скорости (вращение назад) void setMotorSpeed(int left, int right) { // Ограничиваем скорость left = constrain(left, -MAX_SPEED, MAX_SPEED); right = constrain(right, -MAX_SPEED, MAX_SPEED); if (left >= 0) { analogWrite(LEFT_FORWARD, left); analogWrite(LEFT_BACKWARD, 0); } else { analogWrite(LEFT_FORWARD, 0); analogWrite(LEFT_BACKWARD, abs(left)); } if (right >= 0) { analogWrite(RIGHT_FORWARD, right); analogWrite(RIGHT_BACKWARD, 0); } else { analogWrite(RIGHT_FORWARD, 0); analogWrite(RIGHT_BACKWARD, abs(right)); } } void stopMotors() { analogWrite(LEFT_FORWARD, 0); analogWrite(LEFT_BACKWARD, 0); analogWrite(RIGHT_FORWARD, 0); analogWrite(RIGHT_BACKWARD, 0); } void calibrateSensors() { Serial.println("Calibrating... Move sensor over BLACK and WHITE areas."); // Инициализируем "наоборот", чтобы найти реальные мин/макс calib.leftMin = 1023; calib.leftMax = 0; calib.rightMin = 1023; calib.rightMax = 0; unsigned long startTime = millis(); // Крутимся на месте или просто ждем 5 секунд, пока ты водишь роботом по линии // Лучше просто ждать и руками водить робота над линией while (millis() - startTime < 5000) { int l = analogRead(LEFT_SENSOR); int r = analogRead(RIGHT_SENSOR); if (l < calib.leftMin) calib.leftMin = l; if (l > calib.leftMax) calib.leftMax = l; if (r < calib.rightMin) calib.rightMin = r; if (r > calib.rightMax) calib.rightMax = r; // Мигаем светодиодом или выводим в порт if ((millis() / 200) % 2) Serial.print("."); } Serial.println("\nDone."); Serial.print("L: "); Serial.print(calib.leftMin); Serial.print("-"); Serial.println(calib.leftMax); Serial.print("R: "); Serial.print(calib.rightMin); Serial.print("-"); Serial.println(calib.rightMax); stopMotors(); } ``` ### Что изменилось и как это работает: 1. **Нормализация (`map`):** Вместо жесткого порога (Threshold), мы приводим показания каждого датчика к диапазону `0..1000`. * 0 = полностью белый. * 1000 = полностью черный. Это позволяет понять, *насколько* датчик заехал на линию, а не просто "видит/не видит". 2. **Расчет ошибки (`error`):** `error = leftNorm - rightNorm`. * Если робот точно по центру, левый и правый датчики видят (примерно) одинаковый серый край линии. `500 - 500 = 0`. Ошибка ноль. * Если робот уходит вправо, левый датчик видит черное (1000), правый белое (0). Ошибка `1000`. Регулятор повернет влево. 3. **Плавность (`setMotorSpeed`):** Вместо включения мотора на полную или выключения, мы **модулируем** скорость. * Левый мотор = `База - Поправка` * Правый мотор = `База + Поправка` Это позволяет проходить пологие повороты, не теряя скорости, и резко тормозить одну гусеницу только на крутых поворотах. 4. **Восстановление при потере линии:** Добавлен блок `if (leftNorm < 100 && rightNorm < 100)`. Если робот вылетел на чисто белое поле, он вспоминает, куда была последняя ошибка (`lastError`), и начинает вращаться вокруг своей оси в ту сторону, пока снова не найдет линию. ### Как настроить (Тюнинг): 1. **Калибровка:** Раскомментируй `#define CALIBRATION`, загрузи, поводи роботом 5 секунд над черной линией и белым полем (чтобы датчики увидели и самый черный, и самый белый цвет). Закомментируй `#define CALIBRATION` и загрузи снова. 2. **Настройка `Kp` (Сила поворота):** * Поставь `Kd = 0`. * Начни с малого `Kp` (например, 0.05). * Если робот уезжает с поворота прямо — увеличивай `Kp`. * Если робот начинает вилять из стороны в сторону на прямой — `Kp` слишком большой. 3. **Настройка `Kd` (Стабилизация):** * Когда найдешь `Kp`, при котором робот держит линию, но немного виляет, начинай поднимать `Kd` (например, с 0.5 до 2.0). * `Kd` уберет виляние, робот станет ехать «как влитой». Если моторы крутятся не в ту сторону при повороте, поменяй знаки `+ correction` и `- correction` местами в расчете `leftSpeed` и `rightSpeed`.