// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки. #include struct Result; // Тип итогов до прототипов, которые добавляет Arduino. #if defined(ROBOT_TELEMETRY_BUILD_ID) #include robot_telemetry::TelemetryStream courseTelemetry; #endif // Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. #include const int LEFT_SENSOR_PIN = A4; const int RIGHT_SENSOR_PIN = A5; // Измерения белого и чёрного из журнала (урок 3.1). const int LEFT_WHITE = 0; const int LEFT_BLACK = 0; const int RIGHT_WHITE = 0; const int RIGHT_BLACK = 0; const int LEFT_DIR_PIN = 7; const int LEFT_SPEED_PIN = 6; const int RIGHT_DIR_PIN = 4; const int RIGHT_SPEED_PIN = 5; const int FORWARD = HIGH; // Регулятор заезда: 0 — релейный, 1 — П, 2 — ПД, 3 — ПИД. const int MODE = 1; int baseSpeed = 100; // одна скорость для всех регуляторов float kp = 0.5; // твой kp из урока 3.2 float kd = 0; // Д: реакция на то, как быстро уходит линия float ki = 0; // И: реакция на ошибку, которая копится const long SUM_LIMIT = 3000; // предел накопленной ошибки const unsigned long RUN_MS = 6000; // длина заезда: робот должен остановиться до конца дорожки const unsigned long STEP_MS = 10; // шаг регулятора: 100 раз в секунду int lastError = 0; long errorSum = 0; // Итоги заезда: хранятся в памяти Arduino и после выключения. struct Result { int marker; int mode; int baseSpeed; float kp; float kd; float ki; long steps; long sumAbs; int maxAbs; int crossings; }; // ===== Твоя часть: напиши сам ===== // Поправка П-, ПД- или ПИД-регулятора по ошибке этого шага. int controller(int mode, int error) { // Занятие 1. П: поправка пропорциональна ошибке, коэффициент kp. // Занятие 2. Д, режимы 2 и 3: прибавь kd, умноженный на то, насколько ошибка изменилась с прошлого шага. // Занятие 2. И, только режим 3: копи ошибку в errorSum, не выпуская сумму за ±SUM_LIMIT, и прибавь ki × errorSum. // В конце запомни ошибку этого шага в lastError: она нужна Д на следующем шаге. return 0; } // ===== Готовое: шкала, моторы, измерение заезда и запуск — не меняй ===== // Переводит показание в шкалу: чёрное — 0, белое — 100. int level(int value, int white, int black) { long result = (long)(value - black) * 100 / (white - black); return constrain(result, 0, 100); } void drive(int leftDir, int leftSpeed, int rightDir, int rightSpeed) { digitalWrite(LEFT_DIR_PIN, leftDir); digitalWrite(RIGHT_DIR_PIN, rightDir); analogWrite(LEFT_SPEED_PIN, leftSpeed); analogWrite(RIGHT_SPEED_PIN, rightSpeed); } const int MARKER = 2610; // признак того, что в памяти записан заезд Result run; int lastSide = 0; unsigned long startTime; unsigned long nextStep; // Добавляет ошибку шага к итогам заезда. void record(int error) { run.steps++; run.sumAbs += abs(error); if (abs(error) > run.maxAbs) { run.maxAbs = abs(error); } int side = 0; if (error > 10) { side = 1; } if (error < -10) { side = -1; } if (side != 0 && lastSide != 0 && side != lastSide) { run.crossings++; } if (side != 0) { lastSide = side; } } void printResult(Result r) { const char *names[] = {"релейный", "П", "ПД", "ПИД"}; Serial.print("Регулятор: "); Serial.print(names[r.mode]); Serial.print(" скорость="); Serial.print(r.baseSpeed); Serial.print(" kp="); Serial.print(r.kp); Serial.print(" kd="); Serial.print(r.kd); Serial.print(" ki="); Serial.println(r.ki, 3); Serial.print("Средняя ошибка: "); Serial.println(r.steps > 0 ? (float)r.sumAbs / r.steps : 0, 1); Serial.print("Наибольшая ошибка: "); Serial.println(r.maxAbs); Serial.print("Переходов через линию: "); Serial.println(r.crossings); } // Ждёт, пока робот стоит на старте: оба датчика видят белое 1 секунду подряд. void waitForStart() { Serial.println("Поставь робота на старт: линия между датчиками"); unsigned long whiteSince = millis(); while (millis() - whiteSince < 1000) { courseTelemetryPoll(); int leftLevel = level(analogRead(LEFT_SENSOR_PIN), LEFT_WHITE, LEFT_BLACK); int rightLevel = level(analogRead(RIGHT_SENSOR_PIN), RIGHT_WHITE, RIGHT_BLACK); if (leftLevel < 70 || rightLevel < 70) { whiteSince = millis(); } } } void setup() { courseTelemetryBegin(); pinMode(LEFT_DIR_PIN, OUTPUT); pinMode(LEFT_SPEED_PIN, OUTPUT); pinMode(RIGHT_DIR_PIN, OUTPUT); pinMode(RIGHT_SPEED_PIN, OUTPUT); drive(FORWARD, 0, FORWARD, 0); Result last; EEPROM.get(0, last); if (last.marker == MARKER && last.mode >= 0 && last.mode <= 3) { Serial.println("Прошлый заезд:"); printResult(last); } else { Serial.println("Записанных заездов нет"); } // Без измерений робот не поедет. if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) { Serial.println("Впиши измерения белого и чёрного"); while (true) { courseTelemetryPoll(); } } waitForStart(); courseWait(2000); // время убрать руки от робота run = {MARKER, MODE, baseSpeed, kp, kd, ki, 0, 0, 0, 0}; startTime = millis(); nextStep = startTime; } void loop() { if (millis() - startTime >= RUN_MS) { drive(FORWARD, 0, FORWARD, 0); EEPROM.put(0, run); Serial.println("Заезд окончен:"); printResult(run); courseTelemetryReport(analogRead(LEFT_SENSOR_PIN), analogRead(RIGHT_SENSOR_PIN), NAN, NAN); while (true) { courseTelemetryPoll(); } } while (millis() < nextStep) { courseTelemetryPoll(); } nextStep += STEP_MS; int leftRaw = analogRead(LEFT_SENSOR_PIN); int rightRaw = analogRead(RIGHT_SENSOR_PIN); int leftLevel = level(leftRaw, LEFT_WHITE, LEFT_BLACK); int rightLevel = level(rightRaw, RIGHT_WHITE, RIGHT_BLACK); int error = leftLevel - rightLevel; record(error); float observedOutput = NAN; // у релейного алгоритма нет пропорциональной поправки int leftSpeed; int rightSpeed; if (MODE == 0) { // Релейный, как в уроке 3.1: колесо над линией стоит. leftSpeed = leftLevel < 50 ? 0 : baseSpeed; rightSpeed = rightLevel < 50 ? 0 : baseSpeed; } else { int correction = controller(MODE, error); observedOutput = correction; leftSpeed = constrain(baseSpeed + correction, 0, 255); rightSpeed = constrain(baseSpeed - correction, 0, 255); } drive(FORWARD, leftSpeed, FORWARD, rightSpeed); courseTelemetryReport(leftRaw, rightRaw, error, observedOutput); } // ===== Готовое: передача наблюдений — не меняй ===== void courseTelemetryBegin() { #if defined(ROBOT_TELEMETRY_BUILD_ID) using namespace robot_telemetry::wire; courseTelemetry.begin((1UL << Line0) | (1UL << Line1) | (1UL << LineError) | (1UL << ControlOutput)); #else Serial.begin(9600); #endif } void courseTelemetryPoll() { #if defined(ROBOT_TELEMETRY_BUILD_ID) courseTelemetry.update(); #endif } // NAN означает «ещё не вычислено», а не нулевую ошибку или поправку. void courseTelemetryReport(int leftRaw, int rightRaw, float error, float output) { #if defined(ROBOT_TELEMETRY_BUILD_ID) using namespace robot_telemetry::wire; courseTelemetry.setUnsigned(Line0, leftRaw); courseTelemetry.setUnsigned(Line1, rightRaw); if (isnan(error)) courseTelemetry.setMissing(LineError); else courseTelemetry.setFloat(LineError, error); if (isnan(output)) courseTelemetry.setMissing(ControlOutput); else courseTelemetry.setFloat(ControlOutput, output); courseTelemetry.update(); #endif } void courseWait(unsigned long durationMs) { unsigned long started = millis(); while (millis() - started < durationMs) { courseTelemetryPoll(); delay(1); } }