Welcome back to the build! In our last session, we successfully woke our robot from its slumber. We wrote our first lines of code, confirming that we can command each motor and, crucially, that we can “hear” them respond by reading the raw tick counts from their encoders.
This is a huge step, but “ticks” are a raw, abstract unit. If we tell our robot’s brain to achieve a speed of “500 ticks,” what does that even mean? Is it fast? Is it slow? To build an intelligent controller, we need to speak a language it can understand and that we can measure—a standardized unit of speed.
Today, we will translate the raw language of encoder ticks into the universal language of motion: Revolutions Per Minute (RPM). We will write the code that gives our robot a true sense of its own speed, a critical prerequisite for the closed-loop control we’re about to implement.
The Core Concept: Speed is a Rate
At its heart, speed is simply a measure of distance traveled over a period of time. For our rotating wheels, this translates to:
Speed = (Number of Rotations) / (Time Elapsed)
Our encoders don’t give us rotations directly; they give us ticks. So, our formula starts as:
Speed (in Ticks per Second) = (Change in Ticks) / (Change in Time)
This is the value we’ll calculate first. Then, with a little bit of information about our specific hardware, we can convert “ticks per second” into the much more intuitive “revolutions per minute.”
Step 1: Know Your Hardware Constants
To perform this conversion, we need two numbers from the datasheets of the motors you purchased in Session 5. These values are the bridge between the digital world of ticks and the physical world of the spinning wheel.
- Ticks Per Revolution of the Motor (TPR): This is the number of pulses the encoder generates for one single, full 360-degree rotation of the motor’s internal shaft. This is the value before any gearing. For many common DC motors used in hobby robotics, this value is 28. 1
- Gear Ratio: Our motors are geared, meaning the motor itself spins very fast to make the output shaft (where the wheel is attached) spin slowly but with more torque. The gear ratio tells us how many times the motor shaft must turn for the wheel to turn just once. For example, a common gear ratio is 30.21:1. 1
With these two numbers, we can calculate the most important constant for our project: the number of ticks for one full revolution of the wheel.
Ticks Per Wheel Revolution = Ticks Per Motor Revolution × Gear Ratio
1
For our example hardware, this would be:
845.88 = 28 × 30.21
This means our Arduino needs to count about 846 ticks to know that the wheel has completed one full circle.
Step 2: Writing the RPM Calculation Code
Let’s write the Arduino code to perform this calculation. We’ll build on the encoder-reading sketch from Session 7. The strategy is to measure how many ticks have passed over a fixed time interval and use that rate to calculate the RPM.
Copy the following code into your Arduino IDE. Make sure to change the TICKS_PER_MOTOR_REV
and GEAR_RATIO
values to match the specifications of your motors.
C++
// --- Pin Definitions for Front-Left Encoder ---
#define ENCODER_A 21 // Must be an interrupt pin
#define ENCODER_B 20 // Must be an interrupt pin
// --- Hardware Constants ---
// CHANGE THESE VALUES TO MATCH YOUR MOTOR AND GEARBOX
const float TICKS_PER_MOTOR_REV = 28.0;
const float GEAR_RATIO = 30.21;
const float TICKS_PER_WHEEL_REV = TICKS_PER_MOTOR_REV * GEAR_RATIO;
// A 'volatile' variable can be changed by an interrupt
volatile long encoder_ticks = 0;
// Variables for RPM calculation
long previous_ticks = 0;
unsigned long previous_time_us = 0;
float motor_rpm = 0.0;
// Interrupt Service Routine (ISR)
void readEncoder() {
// Read the state of the other encoder pin to determine direction
int b = digitalRead(ENCODER_B);
if (b > 0) {
encoder_ticks++;
} else {
encoder_ticks--;
}
}
void setup() {
Serial.begin(115200); // Use a faster baud rate for more data
Serial.println("RPM Calculator");
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_A), readEncoder, CHANGE);
previous_time_us = micros();
}
void loop() {
// Calculate RPM every 100 milliseconds
if (micros() - previous_time_us >= 100000) {
// --- RPM Calculation ---
// 1. Get the current tick count and time
long current_ticks = encoder_ticks;
unsigned long current_time_us = micros();
// 2. Calculate the change in ticks and time
long delta_ticks = current_ticks - previous_ticks;
unsigned long delta_time_us = current_time_us - previous_time_us;
// 3. Calculate ticks per second
// (delta_ticks / delta_time_us) gives ticks per microsecond.
// Multiply by 1,000,000 to get ticks per second.
float ticks_per_second = (float)delta_ticks / (float)delta_time_us * 1000000.0;
// 4. Calculate revolutions per minute (RPM)
// (ticks_per_second / TICKS_PER_WHEEL_REV) gives revolutions per second.
// Multiply by 60 to get revolutions per minute.
motor_rpm = (ticks_per_second / TICKS_PER_WHEEL_REV) * 60.0;
// --- Update state for the next calculation ---
previous_ticks = current_ticks;
previous_time_us = current_time_us;
// Print the calculated RPM
Serial.print("RPM: ");
Serial.println(motor_rpm);
}
}
Step 3: Testing and Verification
Upload this code to your robot. Open the Serial Monitor (Tools > Serial Monitor
) and make sure the baud rate is set to 115200.
Now, spin the front-left wheel by hand. You should see the RPM value printed to the monitor.
- Spin it slowly, and you should see a low RPM value.
- Spin it quickly, and the RPM value should increase.
- Spin it in the opposite direction, and you should see a negative RPM value.
This confirms our logic is working! The robot can now quantify its own speed. Measuring over a fixed interval (like the 100ms in our code) helps to smooth out the readings and avoid noisy data that can result from measuring over very short time periods. 3
What’s Next?
This session was the final, crucial piece of the puzzle. We now have everything we need for a true closed-loop system:
- A Goal: We can define a target speed, like
target_rpm = 100.0;
- A Measurement: Our code now calculates the
actual_rpm
. - The Error: We can find the difference:
error = target_rpm - actual_rpm;
This error signal is the fuel for our PID controller. In our next session, we will finally combine the theory from Session 3 with the practical code from the last few sessions. We will write the full PID control loop that takes this error and automatically adjusts the motor’s power to make it hit and maintain our target speed, no matter the disturbances. It’s time to close the loop.