Robot courier Lesson 3.2 All coursesFor teachersStudy in the app Student Arduino Uno + L298P motor shield
Topic 3 · Lesson 3.2

P-controller: smooth and fast

The robot turns the more strongly the further the line goes, and passes arcs without jerking.

2 × 45 min1–2 students per robotPractice

Lesson result

The robot moves smoothly along the line with the P-controller and passes the arc of its level. The coefficient and speed are selected and recorded in the log.

You will learn

  • Convert the readings of two sensors to a common scale from 0 to 100.
  • Calculate the error as the difference in readings and explain its sign.
  • Explain how the correction changes the speed of the wheels.
  • Select the coefficient and speed based on observations on the track.

Need to know

  • 3.1 Line sensors and first follow
  • 2.1 Motors: driving and stopping

Will need

Line sensor (analog, Trema) × 2USB cable for Arduino

Why this lesson

In Lesson 3.1, the robot moved along the line in jerks: the wheel was either spinning or standing still. You can’t drive like that on the steep curves of the final task. Today the robot will learn to turn a little bit, when the line went a little, and strongly, when she went far away. This is how autopilots and robot couriers work.

Warm up · remember
  1. How did the program from Lesson 3.1 decide that there is a line under the sensor?
  2. Why did the robot in Lesson 3.1 move jerkily?
  3. What does the function do drive()?
1
Lesson 1 · 45 min
Error instead of "yes or no"
1

Smoothly - how is that?

8 min

What should a robot do. The robot needs to move along the line more smoothly: with a small deviation, correct the course slightly, and with a large deviation, more strongly. We use the same reflection sensors and motors, adding a P-controller - a rule for calculating the correction.

What we use and how it works. First, we convert the measurements of each sensor into a common scale based on its white and black. The difference in levels is called error. The coefficient kp multiplies the error: the greater the deviation, the greater the correction. For one wheel we add it to the base command, for the other we subtract it. The new dimension allows the movement to be adjusted again - this is feedback.

How to link a dimension to an action. Too small an adjustment does not have time to return the robot, too much causes oscillations. We change one parameter at a time and compare the trajectory and readings on the same route. Zero error means equal readings, but not always the correct position: both sensors may lose line.

Imagine that you are driving a bicycle along a line. If the line goes a little to the left, you turn the handlebars a little. If it’s too strong, you turn harder. You don't throw the handlebars from extreme position to extreme position.

  1. Stand with a partner: one is a “robot”, the second holds a line (cord or strip of paper) and smoothly moves it left and right.
  2. The “Robot” turns its body the more, the further the line is from the middle.

If you are alone: ​​put a curved cord on the table and move your palm along it - this is a “robot”. Rotate your palm the more strongly the further the cord moves from its middle.

It should work

You can explain: the strength of the turn depends on how far the line goes.

Imagine that you are driving a bicycle along a line: if you go a little further, you turn the handlebars a little, if you go too far, you turn it more. The program from lesson 3.1 cannot do this: it only knows “there is a line” or “not”.

  1. Run the program from lesson 3.1 on an arc of 350 mm and see how the robot drives.
  2. Write it down in your journal in one sentence: what does the program lack to turn smoothly?
It should work

You can explain: the strength of the turn should depend on how far the line goes.

Research the problem before you solve it.

  1. Run your program from lesson 3.1 on an arc of 350 mm, then on an arc of 125 mm. Record how the robot drives: jerking, wobbling, leaving the line.
  2. Explain in the journal what information from sensors program 3.1 throws out and why this causes the robot to move jerkily.
  3. Before you read further, come up with your own rule: how wheel speeds should depend on sensor readings. Write it down in words or a formula. At the end of the lesson, compare it with the P-controller.
Tip: If you don't know where to start

Program 3.1 compares the reading with the threshold and gets a "yes or no". What if you use the number itself?

It should work

The log contains observations on two arcs, an explanation of the reason for the jerks and your control rule.

2

Common scale for two sensors

15 min

Each sensor has its own white and black numbers. To compare them, let’s convert both to one scale: black - 0, white - 100, the edge of the line is somewhere in the middle.

  1. Create a sketch L32_Error. Transfer your measurements of white and black from the logbook.
Scale and errorL32_Error.ino.ino
// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки.#include <math.h>#if defined(ROBOT_TELEMETRY_BUILD_ID)#include <RobotTelemetry.h>robot_telemetry::TelemetryStream courseTelemetry;#endif 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; // Переводит показание в шкалу: чёрное — 0, белое — 100.int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} void setup() {  courseTelemetryBegin();  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    Serial.println("Впиши измерения белого и чёрного");    while (true) {      courseTelemetryPoll();    }  }} void loop() {  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;   courseTelemetryReport(leftRaw, rightRaw, error, NAN);  Serial.print("L=");  Serial.print(leftLevel);  Serial.print("  R=");  Serial.print(rightLevel);  Serial.print("  ошибка=");  Serial.println(error);  courseWait(200);} // ===== Готовое: передача наблюдений — не меняй =====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);  }}
  1. Upload, open the Serial Monitor. Check: on white both numbers are about 100, on black they are about 0.
  2. Slowly draw the edge of the line under the left sensor. How does the number change?
It should work

Both sensors show about 100 on white and about 0 on black, with intermediate numbers at the edge of the line.

How it works: How the level function works

value - black - how different the reading is from black. white - black - all the way from black to white. Their ratio, multiplied by 100, is “what percentage of the way to white.” The formula works even if the sensor has a black number - a large number, and a white number - a small one. constrain(…, 0, 100) cuts off random outliers outside the scale.

The sensors have different white and black numbers, so they cannot be compared directly. We need a general scale: black - 0, white - 100. The reading on the scale is “how many percent of the path from black to white” it has passed.

  1. Create a sketch L32_Error according to the template, transfer measurements from the log.
  2. Write the function level himself. Error in loop() don't touch it yet.
Template: scale and errorL32_Error_Gaps.ino.ino
// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки.#include <math.h>#if defined(ROBOT_TELEMETRY_BUILD_ID)#include <RobotTelemetry.h>robot_telemetry::TelemetryStream courseTelemetry;#endif // Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. 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; // ===== Твоя часть: напиши сам ===== int level(int value, int white, int black) {  // Шкала: чёрное — 0, белое — 100, между ними — по прямой.  // Работает и когда у датчика чёрное — большое число, и когда маленькое; не выходит за 0…100.  return 0;} void loop() {  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);   // Ошибка: насколько и в какую сторону линия ушла от середины; линия посередине — 0.  float error = NAN; // Пока ошибка не вычислена, график показывает «нет данных».   courseTelemetryReport(leftRaw, rightRaw, error, NAN);  Serial.print("L=");  Serial.print(leftLevel);  Serial.print("  R=");  Serial.print(rightLevel);  Serial.print("  ошибка=");  Serial.println(error);  courseWait(200);} // ===== Готовое: настройка и защита — не меняй ===== void setup() {  courseTelemetryBegin();  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    Serial.println("Впиши измерения белого и чёрного");    while (true) {      courseTelemetryPoll();    }  }} // ===== Готовое: передача наблюдений — не меняй =====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);  }}
Predict

The sensor has white - 120, black - 780. What scale number will you get for reading 450? Do the math before you launch.

  1. Upload and check on white, black and line edge.
  2. Raise the robot 3-4 cm above the table: there is almost nothing for the sensor to reflect. What is the “raw” number, what does the scale show and what part of the function is responsible for this?
  3. Answer in the journal: what type of number did you use for the calculation and why not an ordinary one? int?
Hint: Where to start the formula

The path from black to white - white - black. Passed part - value - black. All that remains is to convert their ratio into percentages.

Hint: The scale jumps at white

On Arduino Uno int only stores numbers up to 32,767. Calculate how many you get (value - black) * 100 for reading 50 and black 780.

Hint: The scale goes beyond 0…100

constrain(x, a, b) returns x, but no less a and no more b.

It should work

On white it is about 100, on black it is about 0, for the reading 450 from the example it is 50. The scale does not go beyond 0...100, and you know why. In the logbook there is an answer about the type of number.

You don't start from scratch: work in to your program from lesson 3.1. It already has contacts, measurements of white and black, and protection against starting without measurements. Save a copy of it as L32_P_Line - during the lesson you will turn it into a P-controller, step by step, according to the requirements.

Hint: Don't have your own program from Lesson 3.1?

Take the frame: it only contains contacts, motor control and protection. You can write the rest yourself.

P-controller frameL32_P_Line_Skeleton.ino.ino
// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки.#include <math.h>#if defined(ROBOT_TELEMETRY_BUILD_ID)#include <RobotTelemetry.h>robot_telemetry::TelemetryStream courseTelemetry;#endif // Каркас П-регулятора. Требования к программе — в тексте урока, шаг за шагом.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; 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);} // Требование 1: шкала «чёрное — 0, белое — 100».int level(int value, int white, int black) {  return 0;} 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);   // Без измерений робот не поедет. Эту защиту не удаляй.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {      courseTelemetryPoll();    }  }  courseWait(2000);} void loop() {  float observedError = NAN;  float observedOutput = NAN;  // Требования 2 и 3: ошибка, поправка и скорости колёс.  // Для графиков сохрани свои результаты в observedError и observedOutput.  drive(FORWARD, 0, FORWARD, 0);  courseTelemetryReport(analogRead(LEFT_SENSOR_PIN), analogRead(RIGHT_SENSOR_PIN),                        observedError, 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);  }}
How it works: Command help
Command or typeWhat does
analogRead(pin)Sensor reading: 0…1023
analogWrite(pin, v)Motor speed: v from 0 to 255
digitalWrite(pin, HIGH) / LOWMotor direction
constrain(x, a, b)x, but no less a and no more b
Serial.begin(9600)Enables the Serial Monitor, writes once per setup()
Serial.print(x) / Serial.println(x)Output to Serial Monitor; println - with line feed
int on Arduino UnoIntegers from −32,768 to 32,767
long on Arduino UnoIntegers from −2,147,483,648 to 2,147,483,647
/ with integersThe fractional part is discarded: 7 / 2 gives 3

Requirement 1. Function level(value, white, black) converts the reading to a scale of 0…100:

  • black - 0, white - 100, between them - in a straight line;
  • also works when the sensor has a black number - a large number, and a white number - a small one;
  • never goes beyond 0...100;
  • does not overflow on Arduino Uno.

Acceptance. Write level in the test sketch, upload it and open the Serial Monitor. The test checks eight cases, including an inverted sensor and readings outside the scale. When all the lines PASS, transfer the function to your program and check the scale of both sensors on the track.

Testing the level functionL32_Level_Test.ino.ino
// Проверка функции level. Напиши функцию здесь, загрузи и открой монитор порта.// Когда все строки PASS, перенеси level в свою программу. // Требование 1: шкала «чёрное — 0, белое — 100».int level(int value, int white, int black) {  return 0;} int failed = 0; void expect(int value, int white, int black, int expected) {  int actual = level(value, white, black);  Serial.print(actual == expected ? "PASS " : "FAIL ");  Serial.print("level(");  Serial.print(value);  Serial.print(", ");  Serial.print(white);  Serial.print(", ");  Serial.print(black);  Serial.print(") = ");  Serial.print(actual);  Serial.print(", ожидаем ");  Serial.println(expected);  if (actual != expected) {    failed++;  }} void setup() {  Serial.begin(9600);   // Датчик, у которого чёрное — большое число, а белое — маленькое.  expect(780, 120, 780, 0);  expect(120, 120, 780, 100);  expect(450, 120, 780, 50);  // За пределами шкалы.  expect(900, 120, 780, 0);  expect(50, 120, 780, 100);  // Датчик, у которого наоборот: белое — большое число, чёрное — маленькое.  expect(120, 900, 120, 0);  expect(510, 900, 120, 50);  expect(900, 900, 120, 100);   if (failed == 0) {    Serial.println("Все проверки пройдены");  } else {    Serial.print("Ошибок: ");    Serial.println(failed);  }} void loop() {}
Hint: Idea (1 of 3)

The scale is the fraction of the path from black to white. The whole path is the difference between white and black.

Hint: Plan (2 of 3)
  1. How far has the reading moved away from black?
  2. Divide by the length of the entire path and multiply by 100.
  3. Crop the result to the scale boundaries.
Hint: Code snippet (3 of 3)

long result = (long)(value - black) * 100 / (white - black); - why here long and why does multiplication come before division?

It should work

The test shows "All checks passed." On the track, the scale shows about 0 on black and about 100 on white.

3

Consider the error

15 min

Error - difference in readings: error = leftLevel - rightLevel. When the line is exactly in the middle, both sensors see the same and the error is zero.

Predict

The line went under the left sensor. What sign will the error have - plus or minus? And if under the right?

  1. Look at the error line in the Serial Monitor.
  2. Move the line under the left sensor, then under the right. Write down the sign and approximate numbers in your journal.
Where is the lineleftLevelrightLevelError
In the middle
Below left
Under the right
It should work

The line on the left is a negative error, on the right is positive, in the middle is near zero.

Error shows how far and in which direction the line has moved from the middle. We consider it as the difference: left reading minus right reading.

  1. Write the error calculation in loop() yourself and upload the sketch.
Predict

The line went under the left sensor. What sign will the error have? Why?

  1. Check the forecast: move the line under the left sensor, then under the right. Write it down in a journal.
Where is the lineleftLevelrightLevelError
In the middle
Below left
Under the right
  1. Answer in the journal: Which direction should the robot turn if the error is negative?
Hint: Error sign

The black line is 0 on the scale. If the line is under the left sensor, which of the two numbers is smaller?

It should work

The signs in the table matched the forecast, and you can explain which way to turn at each sign.

Requirement 2. The program calculates the error - a signed number: how far and in which direction the line has moved from the middle. You choose the sign, but then the correction must turn the robot to line.

  1. Add error calculation and temporary output leftLevel, rightLevel, errors in the Serial Monitor.
  2. Fill out the table and write down your sign rule in one phrase in your journal.
Where is the lineleftLevelrightLevelError
In the middle
Below left
Under the right
  1. Deal with the edge: the line is gone beyond both sensors. What will the error show and what will the robot do then? Write down why it is dangerous on a steep arc.
Hint: Idea (1 of 2)

If the line is exactly in the middle, both sensors see the same thing - the error should be zero.

Hint: Sign Check (2 of 2)

Select: left - right or right - left. Write down which wheel should accelerate when the line is on the left - this will be needed in requirement 3.

It should work

The table is completed, the sign rule is written down. You know what the controller sees when the line goes beyond both sensors.

2
Lesson 2 · 45 min
P-controller on the track
4

From error to speed

10 min

Correction - error multiplied by a coefficient: correction = kp * error. We add correction to the left wheel, and subtract it from the right wheel.

  1. Create a sketch L32_P_Line, transfer measurements.
P-controllerL32_P_Line.ino.ino
// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки.#include <math.h>#if defined(ROBOT_TELEMETRY_BUILD_ID)#include <RobotTelemetry.h>robot_telemetry::TelemetryStream courseTelemetry;#endif 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; // Переводит показание в шкалу: чёрное — 0, белое — 100.int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} 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; float kp = 0.5;     // сила поворота: подбираем на дорожкеint baseSpeed = 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);} 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);   // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {      courseTelemetryPoll();    }  }  courseWait(2000);} void loop() {  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;  int correction = kp * error;   int leftSpeed = constrain(baseSpeed + correction, 0, 255);  int rightSpeed = constrain(baseSpeed - correction, 0, 255);  drive(FORWARD, leftSpeed, FORWARD, rightSpeed);  courseTelemetryReport(leftRaw, rightRaw, error, correction);} // ===== Готовое: передача наблюдений — не меняй =====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);  }}
  1. Calculate on paper: baseSpeed = 100, kp = 0.5, error −40. What speeds will the wheels get?
It should work

You did the math: correction −20, left wheel 80, right wheel 120 - the robot turns left, towards the line.

How it works: Why constrain

With a large error, the speed can turn out to be −30 or 300. The motor only understands 0…255. constrain(число, 0, 255) sets boundaries: less than zero will become 0, more than 255 will become 255.

P-controller rule: correction is proportional to error. The further the line goes, the more one wheel accelerates and the other slows down. How much - sets kp.

  1. Create a sketch L32_P_Line according to the template, transfer the measurements and your function level.
  2. Write loop() himself: error, correction, wheel speeds within 0...255 and command to the motors.
Template: P-controllerL32_P_Line_Gaps.ino.ino
// Наблюдение в MyRobotica включается сборщиком; обычный Arduino работает без библиотеки.#include <math.h>#if defined(ROBOT_TELEMETRY_BUILD_ID)#include <RobotTelemetry.h>robot_telemetry::TelemetryStream courseTelemetry;#endif // Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. 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;float kp = 0.5;     // сила поворота: подбираем на дорожкеint baseSpeed = 100; // скорость на прямой // ===== Твоя часть: напиши сам ===== int level(int value, int white, int black) {  // Перенеси свою функцию level из L32_Error.  return 0;} void loop() {  float observedError = NAN;  float observedOutput = NAN;  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);   // Ошибка — как в L32_Error.  // Поправка пропорциональна ошибке, коэффициент kp.  // Робот поворачивает к линии тем сильнее, чем больше ошибка; скорость каждого колеса — в пределах 0…255.  // Отправь скорости на моторы.  // Сохрани вычисленные ошибку и поправку в observedError и observedOutput для графиков.  courseTelemetryReport(leftRaw, rightRaw, observedError, observedOutput);} // ===== Готовое: моторы, настройка и защита — не меняй ===== 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);} 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);   // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {      courseTelemetryPoll();    }  }  courseWait(2000);} // ===== Готовое: передача наблюдений — не меняй =====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);  }}
  1. Calculate on paper: baseSpeed = 100, kp = 0.5, error −40. What speeds will the wheels get and where will the robot turn? On the stand in the next step you will check this on wheels.
Predict

What will the robot do if you put kp = 0? And if you do kp negative?

Hint: Which wheel to accelerate

The line on the left is a negative error. Which wheel must go faster for the robot to turn toward the line? Match the sign of the amendment with this.

Hint: Speed Limits

The motor only understands 0…255. Which command from the lesson prevents the number from going out of bounds?

It should work

loop() written. For example: correction −20, left wheel 80, right wheel 120 - the robot turns left, towards the line. Forecast about kp = 0 is recorded - check it on the stand.

Requirement 3. The robot moves forward at speed baseSpeed and turns towards the line the more strongly, the larger the error:

  • correction is proportional to error, coefficient kp;
  • kp and baseSpeed - named variables at the beginning of the sketch, they are selected on the track;
  • wheel speeds are always within the range of 0…255;
  • without measurements, the white and black wheels stand (the defense from lesson 3.1 remains).

Acceptance on paper. Calculate the wheel speeds for your program:

baseSpeedkpErrorLeftRight
1000.5−40
2000.860
2001.0100

In the last line, one wheel rests on the border. How much weaker will the robot turn than it “wanted”? Write down in a journal what you can do about it.

Hint: Idea (1 of 3)

One wheel receives the base speed plus an adjustment, the other - minus an adjustment.

Hint: Plan (2 of 3)
  1. Error from requirement 2.
  2. Correction = coefficient × error.
  3. Left and right wheels: base ± correction, each within 0…255.
  4. Send speeds to motors.
Hint: Code snippet (3 of 3)

int leftSpeed = constrain(baseSpeed + correction, 0, 255); - what about the right one? Check the sign according to your rule from requirement 2.

It should work

The program fulfills requirement 3, the table is calculated, the constraint is written to the log.

5

Check on the stand

7 min
  1. Place the robot on the stand, load the program, turn on the power to the motors.
  2. Place a line under the left sensor: the left wheel will slow down, the right wheel will speed up.
  3. Bring it under the right one - vice versa. The line in the middle means the wheels spin the same way.
It should work

The wheels change speed smoothly, rather than “stop and go.”

If it doesn't work
What do you seeWhat to do
Wheels don't spinAre the measurements included? Is the power to the motors turned on?
The wrong wheel is acceleratingCheck the error sign on the Serial Monitor and the speed formula
  1. Place the robot on the stand, load the program, turn on the power to the motors.
  2. Draw a line under the left sensor, then under the right, then in the middle. Write down which wheel is accelerating and which is decelerating.
  3. Check your prediction about kp = 0: put kp = 0, load and move the line. What do wheels do? Return yours kp.
Where is the lineLeft wheelRight wheelCoincided with the forecast?
Below left
Under the right
In the middle
It should work

The wheels change speed smoothly. The table is full, the forecast is about kp = 0 verified.

If it doesn't work
What do you seeWhat to do
Wheels don't spinIs it written loop() with a command to the motors? Are the measurements included?
The wrong wheel is acceleratingCheck the error sign on the Serial Monitor and the speed formula

Acceptance of requirement 3 on a stand.

  1. Temporarily display the error, correction and both wheel speeds in the Serial Monitor.
  2. Place a line under the left sensor, right and in the middle. Does the direction of the amendment match your sign rule from requirement 2?
  3. Find the position of the line at which one wheel rests on 0 or 255. Compare the speeds in the monitor with what you calculated in the requirement table 3.
  4. Turn off measurements (zeros) - are the wheels standing? Return the measurements.
It should work

Correction direction, boundary support and protection were checked using the Serial Monitor. Discrepancies with the table are explained.

6

Select the odds

18 min

Coefficient kp are picked up on the path. Change one number at a time and look what has changed.

  1. Place the robot on the arc track. Try it kp = 0.2, 0.5, 0.8 at one speed.
  2. Log how the robot behaves at each value.
  3. Choose the best kp and try to increase baseSpeed.
kpbaseSpeedWhat is visible
0.2100
0.5100
0.8100
How it works: How to understand where to change kp
  • The robot smoothly moves outward from the arc - the correction is too weak, increase kp.
  • The robot wobbles from side to side even on a straight line - the correction is too strong, reduce kp.
  • Good kp - the robot stays in line on an arc and hardly wobbles on a straight line.
It should work

Picked up kp and baseSpeed, in which the robot passes an arc. The numbers are written in a journal.

Pick up kp himself. Rule one: change one number at a time.

  1. Start with kp = 0.5 at baseSpeed = 100. Then try one clearly smaller and one clearly larger value and narrow the range.
  2. Log at least four attempts.
kpbaseSpeedWhat is visibleWhat to change next and why
0.5100
100
100
100
  1. With the best kp raise baseSpeed. What had to be changed to get the robot to hold the arc again?
Hint: How to understand where to change kp

The robot moves outward from the arc - the correction is weak. The robot wobbles even on a straight line - the correction is strong.

It should work

The journal contains four attempts with an explanation of each step and a pair kp — baseSpeed, on which the robot passes the arc.

Pick up kp and speed method, and not at random, and prove the choice with records.

  1. When baseSpeed = 100 start with kp = 0.1 and double: 0.1, 0.2, 0.4, 0.8... Find the smallest kp, at which the robot begins to wobble on a straight line.
  2. Worker kp take about half as much. Test it on your level arc.
  3. Raise baseSpeed in steps of 20. For each speed - three runs. Remember the last speed at which the robot passed all three.
  4. Write down the table and the conclusion in your journal: why this particular pair, what limits the speed.
  5. Go back to the rule you came up with in the first step. How is it similar to the P-controller and how is it different? What would you change about him now?
kpbaseSpeedRaces (of 3)What is visible
Hint: The robot wobbles at any kp

Check if the wheel is touching 255 or 0 on the arc - remember the last line of the table from requirement 3.

It should work

Table with selection method, pair kp — baseSpeed, tested in three runs, conclusion with justification and comparison of your rule with the P-controller.

7

Quests by level

10 min
Main · Initial field arc

Walk through an arc with a radius of 350 mm, as in the initial field of the final task, without losing the line.

Medium · Speed Arc

Go through an arc with a radius of 250 mm, as in the middle field, at a speed not lower than that selected for the straight line. If that doesn’t work, what’s more important to increase: kp or reduce the speed?

Advanced · Cool S-link

Walk through two arcs with a radius of 125 mm in a row in different directions, as on an advanced field. Start with a couple kp — скорость, selected in the selection step. If it does not hold the S-link, change one parameter and append the existing table with the result; there is no need to rewrite successful and unsuccessful attempts.

Test yourself

Test yourself

Select answers to prepare. They are not stored or evaluated on the site. Take the knowledge test in the course syllabus or discuss the answers with your instructor.

● Basic

1.The left sensor shows 20, the right - 100. What is the error = leftLevel - rightLevel?
2.baseSpeed ​​= 100, kp = 0.5, error = 40. What are the speeds of the wheels?
3.The robot wobbles a lot even on a straight line. What to do with kp?
4.Why convert sensor readings to a 0–100 scale?
5.What does constrain(baseSpeed + correction, 0, 255) do?

★ Intermediate level

1.On an arc with a radius of 250 mm, the robot moves outward at speed. What to try?
2.Why is a stronger correction needed on an arc of smaller radius?
3.The line went under the left sensor, error = leftLevel - rightLevel is negative. Which wheel must speed up for the robot to return to the line?
4.Why in the level function is the result calculated in long and not int?
5.The team immediately changed kp and baseSpeed, and the robot began to pass the arc. What's wrong with this experience?

★★ Advanced level

1.On black and at the edge of the line the scale is correct, but for reading 50 with white = 120 and black = 780 the function returns the wrong number. What's wrong?
int level(int value, int white, int black) {  int result = (value - black) * 100 / (white - black);  return constrain(result, 0, 100);}
2.What will this function return for a reading of 450 with white = 120 and black = 780?
int level(int value, int white, int black) {  long result = (long)(value - black) / (white - black) * 100;  return constrain(result, 0, 100);}
3.The error was calculated as rightLevel - leftLevel, and the correction was still added to the left wheel. The line went under the left sensor. What will the robot do?
4.On a steep arc, the line went beyond both sensors. What will the P-controller do from the lesson?
5.baseSpeed = 200, kp = 1.0, error = 100, speeds are limited to 0...255. What will happen to the turn?

Answer questions at your level. Correct answers needed: Beginner - 4 out of 5; Average - 4 out of 5; Advanced - 4 out of 5. Answers will be checked by the teacher or course program.

Summary

The robot turns in proportion to the error - this is a P-controller. It is smoother than the stop-go rule and goes through tight curves. Next, the robot will learn to find intersections on the line.

How to confirm the result

For each experiment, save the conditions, program version, observation and conclusion. When comparing attempts, change one reason at a time; unsuccessful attempts also remain in the log. Marking a result refers to actions that have already been completed; there is no need to repeat them for the sake of marking. If the result is not obtained, write down the discrepancy and the next hypothesis to be tested. A model, measurements of a real robot, and an explanation are different types of evidence.

Lesson vocabulary

errorHow far the line has moved from the middle: the difference in the readings of the left and right sensors.
correctionHow much to change the wheel speeds to return to the line.
coefficientNumber kp: how many times the correction is greater or less than the error.
P-controllerThe “correction is proportional to the error” rule: the more we went, the more we turn.
constrainA command that prevents a number from going out of bounds, for example beyond 0...255.