Encoders: counting revolutions and exact distance
The robot counts the revolutions of the wheels and travels the measured distance, and not “how much it can do in time.”
Lesson result
The program counts wheel revolutions using encoders and drives the measured distance. The number of pulses per revolution is measured, the accuracy is checked with a ruler. Not needed for the final task.
You will learn
- Explain how an encoder converts wheel rotations into pulses.
- Measure the number of pulses per wheel revolution.
- Convert the distance into impulses using the circumference of the wheel.
- Drive the measured distance and evaluate the accuracy with a ruler.
Need to know
- 2.1 Motors: driving and stopping
- 3.2 P-controller: smooth and fast
Will need
Why this lesson
In lesson 2.1, the robot used timed movement: the motors ran for two seconds. How far it travels in two seconds depends on battery charge, floor surface and weight. Encoder counts wheel revolutions, so the program can select the stopping moment based on wheel rotation, and not just time. You still need to check the accuracy of the path with a ruler.
For the lesson you need an additional module: an encoder for each wheel. It is not included in the main course package; You can't teach a lesson without it. The elective is not needed for the final task.
- How did the robot drive through a given area in Lesson 2.1?
- Why does a robot with dead batteries travel less in the same two seconds?
- What is the P-controller from lesson 3.2 in one phrase?
Why time is a bad ruler
8 minWhat should a robot do. The robot needs to drive a given distance more accurately than using a stopwatch. To do this, you will need additional encoders: they are not included in the basic kit. They measure the rotation of the wheels, not the distance to the obstacle.
What we use and how it works. The encoder produces pulses when rotating. The counter accumulates them; The number of pulses per revolution relates the count to the wheel revolutions. The circumference relates the revolutions to the intended path. An interrupt allows the counter to be incremented quickly on a pulse, even when the main program is busy with other work.
How to link a dimension to an action. Let's measure the impulses per revolution and use the diameter of our wheel. On an AVR, reading a multibyte counter can be interrupted in the middle by an interrupt, so we copy it with interrupts briefly turned off. Let's check the result with a ruler: when slipping, the wheel rotates, but the body moves less, so the encoder does not give an absolute coordinate.
- Remember the program from lesson 2.1, which moves forward in time. Run it twice: at full speed and at half speed. The time is the same.
- Mark where the robot stopped both times. Measure both paths with a ruler and write them down in your journal.
You see: given the same time, the robot travels different distances.
- Run the “timed” program from lesson 2.1 three times: speed 100, 150 and 200 at the same time. Measure the paths with a ruler.
- Answer in the journal: what, besides the speed in the program, changes the path at the same time? Give at least three reasons.
Three paths measured, reasons recorded.
- Measure the paths “in time” at speeds of 100, 150 and 200. Construct a “speed - path” table in the journal.
- Evaluate: is it possible to recalculate the time for 50 cm at a speed of 120 using this table? How much do you trust such a recalculation tomorrow, on a different battery charge? Explain.
- Write down what you need measure while driving, so that the path does not depend on these reasons.
The table, the conversion estimate and the answer are in the journal.
How the encoder works
12 minThe encoder is a disk with slots on the wheel axle and a “fork” sensor. Inside the plug there is an LED on one side and a receiver on the other. When a slot passes by, light hits the receiver and the sensor sends impulse. One revolution of the wheel - as many impulses as there are slots on the disk.
- Count the slots on the encoder disk. Write it down in a journal.
- Find in your robot's wiring table which pins the encoders are connected to. Write down the numbers.
Do not connect encoders while the power is on. If the encoder contacts are not indicated in the wiring table, the connection is made by the teacher.
In the log - the number of slots and contact numbers of the left and right encoders.
How it works: What is an interrupt
The pulses come quickly - tens per second. If the program is busy with something else, it may skip them. Therefore the score is made interrupt: for each pulse, the Arduino itself postpones the main program, adds 1 to the counter and returns back. On Arduino Uno, only pins 2 and 3 can do this.
Encoder - a disk with slots on the wheel axle and an optical gap sensor: the slot passes by the sensor - the sensor gives impulse. The pulses will be counted interrupt: The Arduino itself calls a short function on every pulse, no matter what the main program does.
- Count the slots on the disk, write down the encoder contact numbers from the wiring table.
Wheel with a diameter of 45 mm, 20 slots on the disk. How many millimeters does the wheel travel between two pulses? Do the math before you launch.
- Answer in the journal: why the pulse counter cannot be increased by
loop()afterdelay(200)?
Do not connect encoders while the power is on. Contacts are not listed in the table - the teacher connects.
Hint: How much does a wheel travel per revolution?
In one revolution, the wheel covers the length of its circumference: π × diameter.
Number of slots, contacts, mm per impulse forecast and answer about delay - in the journal.
- Count the slots, write down the encoder contacts from the wiring table.
- Estimate the pulse frequency: 45 mm wheel, 20 slots, robot travels 0.3 m/s. How many pulses per second does each encoder produce?
- Explain in the journal why counting is done as an interrupt and why on the Arduino Uno only pins 2 and 3 are suitable for this.
Do not connect encoders while the power is on. Contacts are not listed in the table - the teacher connects.
Hint: Pulse frequency
First, how many revolutions per second does the wheel make at 300 mm/s.
Frequency estimates and explanation are in the journal.
Pulses per revolution
22 min- Create a sketch
LF1_Count. Enter the encoder contact numbers.
// Контакты энкодеров: впиши номера из таблицы проводки своего робота.// На Arduino Uno счёт по прерыванию работает только на контактах 2 и 3.const int LEFT_ENCODER_PIN = -1;const int RIGHT_ENCODER_PIN = -1; // Счётчики импульсов. volatile: их меняет прерывание.volatile long leftTicks = 0;volatile long rightTicks = 0; // Arduino вызывает её сама на каждый импульс левого энкодера.void countLeft() { leftTicks++;} // То же для правого энкодера.void countRight() { rightTicks++;} void setupEncoders() { // Без контактов энкодеров программа дальше не пойдёт. if (LEFT_ENCODER_PIN < 0 || RIGHT_ENCODER_PIN < 0) { Serial.println("Впиши контакты энкодеров"); while (true) { } } pinMode(LEFT_ENCODER_PIN, INPUT); pinMode(RIGHT_ENCODER_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER_PIN), countLeft, RISING); attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER_PIN), countRight, RISING);} void setup() { Serial.begin(9600); setupEncoders();} void loop() { // Читаем оба счётчика, пока прерывания выключены: число не изменится посередине чтения. noInterrupts(); long left = leftTicks; long right = rightTicks; interrupts(); Serial.print("Левое: "); Serial.print(left); Serial.print(" правое: "); Serial.println(right); delay(200);}
- Upload and open the Serial Monitor. Turn the wheel with your hand: the number should increase.
- Make a mark on the wheel with chalk or a marker. Spin the wheel by hand exactly 10 turns by label. Divide the number of pulses by 10 - this is pulses per revolution.
- Repeat for the second wheel. Write both numbers in your journal.
In the log - impulses per revolution for each wheel. This is usually the number of slots on the disc.
| What do you see | What to do |
|---|---|
| “Enter the encoder contacts” | Enter the contact numbers instead of −1 |
| The number is not growing | Check the wires and that the disc fits through the sensor slot |
| The number increases when the wheel is stationary | The wheel has stopped at the edge of the slot: move it a little |
- Create a sketch
LF1_Countaccording to the template. Enter your contacts, write your part.
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. // Контакты энкодеров: впиши номера из таблицы проводки своего робота.// На Arduino Uno счёт по прерыванию работает только на контактах 2 и 3.const int LEFT_ENCODER_PIN = -1;const int RIGHT_ENCODER_PIN = -1; // Счётчики импульсов. volatile: их меняет прерывание.volatile long leftTicks = 0;volatile long rightTicks = 0; // ===== Твоя часть: напиши сам ===== void countLeft() { // Каждый импульс левого энкодера увеличивает его счётчик на 1.} void countRight() { // То же для правого энкодера.} void loop() { long left = 0; long right = 0; // Скопируй оба счётчика в left и right так, чтобы прерывание не изменило их посередине чтения. Serial.print("Левое: "); Serial.print(left); Serial.print(" правое: "); Serial.println(right); delay(200);} // ===== Готовое: подключение энкодеров — не меняй ===== void setupEncoders() { // Без контактов энкодеров программа дальше не пойдёт. if (LEFT_ENCODER_PIN < 0 || RIGHT_ENCODER_PIN < 0) { Serial.println("Впиши контакты энкодеров"); while (true) { } } pinMode(LEFT_ENCODER_PIN, INPUT); pinMode(RIGHT_ENCODER_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER_PIN), countLeft, RISING); attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER_PIN), countRight, RISING);} void setup() { Serial.begin(9600); setupEncoders();}
- Spin each wheel by hand exactly 10 revolutions along the mark and count the pulses per revolution.
- Compare with the number of slots. Coincidence? If not, find the reason.
Hint: How not to spoil a number when reading
Counter - long, these are 4 bytes, and the Arduino reads them one at a time. An interruption in the middle will give a mixture of the old and new number. There are a couple of commands that temporarily disable interrupts.
The counting works, the pulses per revolution of both wheels are recorded and compared with the number of slots.
Requirement 1. The program counts the pulses of each wheel as an interrupt and prints both counters to the Serial Monitor; The counters are read in such a way that an interrupt does not spoil the number. Without entered contacts, the program does not proceed further.
- Write the program yourself.
How it works: Command help
| Command | What does |
|---|---|
attachInterrupt(digitalPinToInterrupt(pin), f, RISING) | Call a function f for each edge of the pulse on the contact pin |
volatile long x | Variable that is changed by the interrupt |
noInterrupts() / interrupts() | Turn interrupts off and on again |
- Measure the pulses per revolution: spin the wheel 10 revolutions along the mark, then 20 turns. How different are the two results? Which one is more accurate and why?
- Find the position of the wheel at which the score increases without moving. Explain in the log what is happening in the sensor.
Hint: Idea (1 of 2)
Interrupt functions should be short: only increment the counter.
Hint: Counting without moving (2 of 2)
At the edge of the slot, the light hits the receiver “halfway”, and the signal trembles near the threshold.
The program fulfills requirement 1, the pulses per revolution are measured twice, the explanation of the jitter is recorded.
Millimeters to pulses
15 minIn one revolution the wheel covers the circumference: π × диаметр. Wheel diameter - 45 mm. To travel 500 mm, you need 500 / (π × 45) revolutions, and pulses - in TICKS_PER_REV times more.
- Count on paper: how many pulses per 500 mm, if there are 20 pulses per revolution?
- Create a sketch
LF1_Distance, enter the contacts and your number of pulses per revolution.
// Контакты энкодеров: впиши номера из таблицы проводки своего робота.// На Arduino Uno счёт по прерыванию работает только на контактах 2 и 3.const int LEFT_ENCODER_PIN = -1;const int RIGHT_ENCODER_PIN = -1; 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; const int TICKS_PER_REV = 0; // импульсов на оборот колеса: измерь и впишиconst int WHEEL_DIAMETER_MM = 45;const int DISTANCE_MM = 500; // сколько проехатьint speed = 100; volatile long leftTicks = 0;volatile long rightTicks = 0; void countLeft() { leftTicks++;} void countRight() { rightTicks++;} // Сколько импульсов нужно, чтобы проехать mm миллиметров.long ticksFor(int mm, int ticksPerRev, int diameterMm) { float revolutions = mm / (PI * diameterMm); return (long)(revolutions * ticksPerRev + 0.5);} // Сколько импульсов в среднем насчитали оба колеса.long averageTicks() { noInterrupts(); long sum = leftTicks + rightTicks; interrupts(); return sum / 2;} 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 setupEncoders() { // Без контактов энкодеров программа дальше не пойдёт. if (LEFT_ENCODER_PIN < 0 || RIGHT_ENCODER_PIN < 0) { Serial.println("Впиши контакты энкодеров"); while (true) { } } pinMode(LEFT_ENCODER_PIN, INPUT); pinMode(RIGHT_ENCODER_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER_PIN), countLeft, RISING); attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER_PIN), countRight, RISING);} void setup() { Serial.begin(9600); 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); setupEncoders(); // Без числа импульсов на оборот робот не поедет. if (TICKS_PER_REV <= 0) { Serial.println("Впиши число импульсов на оборот"); while (true) { } } delay(2000); // время убрать руки от робота long target = ticksFor(DISTANCE_MM, TICKS_PER_REV, WHEEL_DIAMETER_MM); drive(FORWARD, speed, FORWARD, speed); while (averageTicks() < target) { } drive(FORWARD, 0, FORWARD, 0); delay(1000); // робот докатывается Serial.print("Цель, импульсов: "); Serial.println(target); Serial.print("Насчитано после остановки: "); Serial.println(averageTicks());} void loop() {}
- Place the robot on the floor at the start mark, turn on the power to the motors. The robot will pass and stop. Measure the path with a ruler from the mark to the same point on the body.
You did the math: about 71 pulses. The robot traveled approximately 50 cm, the path was measured.
- Create a sketch
LF1_Distanceaccording to the template, write the contacts and pulses on the back. - Write
ticksForandaverageTicksby goals.
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. // Контакты энкодеров: впиши номера из таблицы проводки своего робота.// На Arduino Uno счёт по прерыванию работает только на контактах 2 и 3.const int LEFT_ENCODER_PIN = -1;const int RIGHT_ENCODER_PIN = -1; 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; const int TICKS_PER_REV = 0; // импульсов на оборот колеса: измерь и впишиconst int WHEEL_DIAMETER_MM = 45;const int DISTANCE_MM = 500; // сколько проехатьint speed = 100; volatile long leftTicks = 0;volatile long rightTicks = 0; // ===== Твоя часть: напиши сам ===== long ticksFor(int mm, int ticksPerRev, int diameterMm) { // Сколько оборотов колеса нужно на mm миллиметров: один оборот — длина окружности колеса. // Сколько это импульсов, округлённо до целого. Число PI в Arduino уже есть. return 0;} long averageTicks() { // Среднее двух счётчиков. Читай их так, чтобы прерывание не изменило число посередине. return 0;} // ===== Готовое: энкодеры, моторы и поездка — не меняй ===== void countLeft() { leftTicks++;} void countRight() { rightTicks++;} 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 setupEncoders() { // Без контактов энкодеров программа дальше не пойдёт. if (LEFT_ENCODER_PIN < 0 || RIGHT_ENCODER_PIN < 0) { Serial.println("Впиши контакты энкодеров"); while (true) { } } pinMode(LEFT_ENCODER_PIN, INPUT); pinMode(RIGHT_ENCODER_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER_PIN), countLeft, RISING); attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER_PIN), countRight, RISING);} void setup() { Serial.begin(9600); 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); setupEncoders(); // Без числа импульсов на оборот робот не поедет. if (TICKS_PER_REV <= 0) { Serial.println("Впиши число импульсов на оборот"); while (true) { } } delay(2000); // время убрать руки от робота long target = ticksFor(DISTANCE_MM, TICKS_PER_REV, WHEEL_DIAMETER_MM); drive(FORWARD, speed, FORWARD, speed); while (averageTicks() < target) { } drive(FORWARD, 0, FORWARD, 0); delay(1000); // робот докатывается Serial.print("Цель, импульсов: "); Serial.println(target); Serial.print("Насчитано после остановки: "); Serial.println(averageTicks());} void loop() {}
How many pulses will return ticksFor(500, 20, 45)? Do the math before you launch.
- Start the trip from the mark, measure the path with a ruler. Compare with the Serial Monitor: how much more “counted after stopping” than the target?
Hint: Round to the nearest whole number
If you add 0.5 and discard the fractional part, you will get rounding to the nearest integer.
The forecast was correct: 71. The robot traveled about 50 cm, the roll after stopping is visible in the Serial Monitor.
Requirement 2. Function long ticksFor(int mm, int ticksPerRev, int diameterMm) converts millimeters to pulses, rounded to the nearest integer and does not overflow on the Arduino Uno.
Acceptance. Write a function in a test sketch. When all the lines PASS, transfer it to your program.
// Проверка функции ticksFor. Напиши функцию здесь, загрузи и открой монитор порта.// Когда все строки PASS, перенеси ticksFor в свою программу. // Требование 2: сколько импульсов нужно, чтобы проехать mm миллиметров (округлённо).long ticksFor(int mm, int ticksPerRev, int diameterMm) { return 0;} int failed = 0; void expect(int mm, int ticksPerRev, int diameterMm, long expected) { long actual = ticksFor(mm, ticksPerRev, diameterMm); Serial.print(actual == expected ? "PASS " : "FAIL "); Serial.print("ticksFor("); Serial.print(mm); Serial.print(", "); Serial.print(ticksPerRev); Serial.print(", "); Serial.print(diameterMm); Serial.print(") = "); Serial.print(actual); Serial.print(", ожидаем "); Serial.println(expected); if (actual != expected) { failed++; }} void setup() { Serial.begin(9600); expect(0, 20, 45, 0); expect(141, 20, 45, 20); // почти ровно один оборот expect(500, 20, 45, 71); // 70.7 округляется вверх expect(1000, 20, 65, 98); // другое колесо expect(1000, 40, 45, 283); expect(2000, 40, 45, 566); // 2000 × 40 не помещается в int if (failed == 0) { Serial.println("Все проверки пройдены"); } else { Serial.print("Ошибок: "); Serial.println(failed); }} void loop() {}
Requirement 3. The program moves forward until average of two counters does not reach the target, stops and prints the target and counted after stopping. Without the entered number of pulses per revolution, the robot stands still.
- Fulfill requirements 2 and 3, drive 500 mm from the mark and measure the path.
Hint: Idea (1 of 2)
Pulses = revolutions × pulses per revolution, revolutions = path / circumference.
Hint: Test fails at 2000 mm (2 of 2)
2000 * 40 - 80,000, more than it can hold int. Count in float or long before multiplication.
Test passed, 500mm trip done and measured.
Why does the robot drive further
15 minAfter the “stop” command, the robot rolls a little more - like a bicycle if you stop pedaling.
- Do three runs of 500 mm. Record the paths in a journal.
- Reduce
speedto 70 and do three more runs. Has it become more accurate?
speed | Heat 1 | Heat 2 | Heat 3 |
|---|---|---|---|
| 100 | |||
| 70 |
The table is full. You see at what speed the robot is more accurate.
- Do three runs of 500mm. For each, write down the path with a ruler and the “roll” in pulses from the Serial Monitor.
- Add an amendment to the program: stop at
OVERSHOOTpulses before the target, whereOVERSHOOT- average roll of three runs. Do three more runs. - Answer in the journal: will this correction be correct at a different speed? Why?
Six races in the log, the correction has been checked, the answer about speed has been written down.
Requirement 4. For SLOW_TICKS impulses to the target, the robot slows down in order to roll less after stopping.
- Complete requirement 4 and pick up
SLOW_TICKSand reduced speed. - Compare three runs without slowing down and three with slowing down: average error and spread.
- Explain in the journal why deceleration is better than the “stop N pulses earlier” correction.
Requirement 4 is met, the comparison of the two methods and the conclusion are in the journal.
Quests by level
12 minDrive 500 mm with an error of no more than 2 cm in three out of three runs.
Drive 1 m, aligning the wheels according to the difference in impulses: if the left one counts more, slow down the left one and speed up the right one, like the P-controller from lesson 3.2. The robot moves away from the straight line no more than 3 cm.
Turn 90° in place: the wheels turn in opposite directions, and each travels a quarter-circle whose diameter equals the robot’s track width — 90 mm. Calculate the pulse counts and check the angle with a set square. The error must not exceed 10°.
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
★ Intermediate level
★★ Advanced level
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 encoder turns wheel revolutions into impulses, and impulses into distance traveled. The robot travels not “as long as it can”, but as long as necessary, regardless of the battery charge.
How to confirm the result
The result of the experiment relates the conditions, program version, observation and conclusion. Comparing attempts with one change helps verify the cause, including failed attempts. The mark refers to practice already completed; repetition for the sake of marking is not necessary. The discrepancy with the forecast sets the next hypothesis to be tested. A model, measurements of a real robot, and an explanation are different types of evidence.
Lesson vocabulary
| encoder | RPM sensor: a disc with slots on the wheel axle and a sensor that gives an impulse to each slot. |
| impulse | Short encoder signal: the slot has passed the sensor. |
| interrupt | Arduino postpones the main program and immediately executes a short function when a pulse arrives at the pin. |
| volatile | Marking the variable that is changed by the interrupt: the program reads it again each time. |
| circumference | Wheel path per revolution: π × diameter. |