**Анализ конструкции и проблем:** Судя по фото и коду, я вижу несколько моментов, которые мешают роботу стабильно ехать медленно и не терять линию: 1. **Расположение датчиков:** Датчики вынесены далеко вперед относительно ведущих колес, а поворотное колесо (ролик) тоже спереди. Это создает большой «рычаг». Малейший поворот колес вызывает сильное смещение датчиков в сторону. Текущий алгоритм (релейный: "вижу черное слева — резко поворачиваю влево") вызывает раскачку (осцилляцию), из-за которой робот вылетает с линии. 2. **Проблема минимальной скорости:** Желтые моторы (TT motors) имеют "мертвую зону". Они могут не крутиться при ШИМ < 60, но при 70 уже едут слишком быстро. Пытаться ехать на ШИМ 40-50 бесполезно — момента не хватит. 3. **Алгоритм:** Ваша текущая логика: «Если оба датчика на белом — едь прямо». Это главная ошибка. На повороте робот сначала смещается одним датчиком на линию, подруливает, но если инерция проносит его чуть дальше, оба датчика оказываются на белом *за пределами* линии. Робот думает "я на прямой" и уезжает в пустоту. --- **Решение:** Я перепишу код, используя **Пропорциональный регулятор (P-регулятор)**. Это позволит роботу поворачивать плавно: чем сильнее он уходит с линии, тем сильнее он подруливает. Также я добавлю "память", чтобы при потере линии он знал, в какую сторону вращаться. **Что изменено:** 1. **Управление моторами:** Вместо полной остановки одного колеса (что вызывает рывки), мы будем *замедлять* одно и *ускорять* другое. 2. **Память:** Если оба датчика видят белое, робот не едет прямо, а продолжает поворачивать в ту сторону, где последний раз видел линию. 3. **ШИМ:** Ограничен до 180, как вы просили. Минимальная скорость выставлена в зону уверенного старта моторов, но регулируется коэффициентами. **Калибровка:** Я сохранил чтение из EEPROM. **Но!** Для П-регулятора важно, чтобы значения были нормализованы. Если калибровка была сделана давно или при другом освещении, робот будет ехать криво. *Если робот ведет себя странно, раскомментируйте `#define CALIBRATION` один раз, прошейте, дайте ему откалиброваться, а затем закомментируйте и прошейте снова.* Вот оптимизированный код: ```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 // --- НАСТРОЙКИ СКОРОСТИ И РЕГУЛЯТОРА --- // Базовая скорость (когда едем прямо). // Для медленной езды ставьте 80-100. Если моторы гудят и не едут, увеличьте. #define BASE_SPEED 90 // Максимальный ШИМ (по вашему запросу не более 180) #define MAX_PWM 180 // Коэффициент чувствительности поворота (Kp). // Если робот виляет (осциллирует) - уменьшайте (0.3, 0.2). // Если робот не вписывается в поворот - увеличивайте (0.6, 0.8). float Kp = 0.5; // --- РЕЖИМ КАЛИБРОВКИ --- // Раскомментируйте, чтобы запустить калибровку (один раз), затем закомментируйте обратно. // #define CALIBRATION struct { uint16_t leftMin, leftMax; // Min = Белый (обычно), Max = Черный uint16_t rightMin, rightMax; } calib; int lastError = 0; // Для запоминания направления при потере линии void calibrateSensors(); void setMotorSpeed(int leftSpeed, int rightSpeed); 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); setMotorSpeed(0, 0); #ifdef CALIBRATION calibrateSensors(); EEPROM.put(0, calib); Serial.println("Calibration saved. Upload code without #define CALIBRATION."); while(1) { // Остановить выполнение после калибровки digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); } #else EEPROM.get(0, calib); // Проверка на "мусор" в EEPROM if (calib.leftMax == 0 || calib.leftMax == 65535) { Serial.println("EEPROM empty! Please calibrate first."); while(1); } #endif } void loop() { // 1. Считываем датчики int rawLeft = analogRead(LEFT_SENSOR); int rawRight = analogRead(RIGHT_SENSOR); // 2. Приводим значения к диапазону 0..1000 // constrain нужен, чтобы не выйти за пределы, если освещение изменилось long mapLeft = map(rawLeft, calib.leftMin, calib.leftMax, 0, 1000); long mapRight = map(rawRight, calib.rightMin, calib.rightMax, 0, 1000); mapLeft = constrain(mapLeft, 0, 1000); mapRight = constrain(mapRight, 0, 1000); // 3. Логика определения ошибки // Предполагаем, что линия черная (высокое значение), фон белый (низкое). // error > 0 -> Линия слева (надо влево) // error < 0 -> Линия справа (надо вправо) int error = mapLeft - mapRight; // --- ОБРАБОТКА ПОТЕРИ ЛИНИИ --- // Если оба датчика видят белое (значения близки к 0), мы потеряли линию. // Порог 200 подбирается экспериментально (это 20% от черного). if (mapLeft < 200 && mapRight < 200) { // Вспоминаем, какой был знак ошибки в последний раз if (lastError > 0) { // Линия была слева, крутимся влево на месте (или резкий поворот) // Чтобы развернуться на месте, левое колесо назад, правое вперед setMotorSpeed(-90, 90); } else { // Линия была справа, крутимся вправо setMotorSpeed(90, -90); } return; // Пропускаем остальной код цикла } // Сохраняем текущую ошибку как "последнюю известную" lastError = error; // 4. Расчет П-регулятора int correction = error * Kp; // 5. Расчет скоростей моторов // При ошибке > 0 (надо влево): correction положителен -> leftSpeed уменьшается, rightSpeed увеличивается int leftSpeed = BASE_SPEED - correction; int rightSpeed = BASE_SPEED + correction; // 6. Ограничение скоростей leftSpeed = constrain(leftSpeed, -MAX_PWM, MAX_PWM); rightSpeed = constrain(rightSpeed, -MAX_PWM, MAX_PWM); // 7. Подача на моторы setMotorSpeed(leftSpeed, rightSpeed); // Небольшая задержка для стабильности АЦП (опционально) delay(2); } // Удобная функция управления моторами // speed: от -180 (назад) до 180 (вперед) void setMotorSpeed(int leftSpeed, int rightSpeed) { // Левый мотор if (leftSpeed > 0) { analogWrite(LEFT_FORWARD, leftSpeed); analogWrite(LEFT_BACKWARD, 0); } else if (leftSpeed < 0) { analogWrite(LEFT_FORWARD, 0); analogWrite(LEFT_BACKWARD, -leftSpeed); // Модуль числа } else { analogWrite(LEFT_FORWARD, 0); analogWrite(LEFT_BACKWARD, 0); } // Правый мотор if (rightSpeed > 0) { analogWrite(RIGHT_FORWARD, rightSpeed); analogWrite(RIGHT_BACKWARD, 0); } else if (rightSpeed < 0) { analogWrite(RIGHT_FORWARD, 0); analogWrite(RIGHT_BACKWARD, -rightSpeed); } else { analogWrite(RIGHT_FORWARD, 0); analogWrite(RIGHT_BACKWARD, 0); } } // Алгоритм калибровки (сохраняем МИН и МАКС значения) void calibrateSensors() { Serial.println("Start Calibration..."); // Инициализируем "наоборот", чтобы найти реальные min/max calib.leftMin = 1023; calib.leftMax = 0; calib.rightMin = 1023; calib.rightMax = 0; // Вращаемся на месте, чтобы пересечь линию несколько раз // Включаем вращение setMotorSpeed(100, -100); unsigned long startTime = millis(); while (millis() - startTime < 4000) { // Калибруемся 4 секунды 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; delay(5); } setMotorSpeed(0, 0); // Добавляем небольшой отступ (шум), чтобы не делить на 0 и иметь четкие границы // calib.leftMin += 10; // calib.leftMax -= 10; 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); } ``` ### Как настроить этот код под вашего робота: 1. **Калибровка:** * Так как я изменил структуру `calib` (вместо порогов я храню Min и Max для `map`), вам **нужно один раз провести калибровку**. * Раскомментируйте `#define CALIBRATION` в начале. * Залейте код. * Поставьте робота на линию. Робот начнет крутиться вокруг своей оси 4 секунды. Он должен "просканировать" и черное, и белое. * После остановки закомментируйте `#define CALIBRATION` и залейте код снова. 2. **Настройка `BASE_SPEED` (Базовая скорость):** * Я поставил `90`. Это примерно половина от максимума (180). Если робот едет слишком быстро, попробуйте снизить до `70-80`. * **Важно:** Если поставить слишком мало (например, 40), робот будет гудеть, но не поедет, пока ошибка не станет большой. Это вызовет рывки. Лучше ехать чуть быстрее, но плавно. 3. **Настройка `Kp` (Коэффициент):** * Если робот "вихляет" на прямой (едет змейкой) -> **уменьшите** `Kp` (например, до 0.3). * Если робот на повороте уезжает прямо и теряет линию -> **увеличьте** `Kp` (например, до 0.7 или 1.0). Этот алгоритм пытается удержать разницу между датчиками равной нулю (то есть держать линию ровно между ними), что гораздо эффективнее простого переключения состояний.