In this module students will:
- Learn how to make the robot drive at different speeds
- Understand the relationship between the wheel speeds and the robot's motion
- Be introduced to motor encoders and how to use them to measure the robot's movements
- Learn about the built in driving functions which help make complicated motions easy
At the end of this module, students will be able to...
- Make the robot drive in straight lines, turn corners, and turn in place
- Write and use basic functions in the Python programming language
- Use different types of loops in Python
- Break down a large, repetitive movement sequence into components and then convert into code
The main body of your Rosmo is called the drivetrain. We call it this because it holds the two wheels which drive your robot around. Specifically, the Rosmo has a differential drivetrain. This is the same kind of drivetrain that you would see on a skid-steer loader.
.. image::  :width: 300
.. image:: 
Run the code and see what happens.
Let's break down the code line by line:
:code:from RosmoLib.defaults import *
tells your robot to load code from
RosmoLib. Don't worry too much about what all the commands in this line mean
right now, just know that you'll put this line at the top of most of your Rosmo
programs.
:code:left_motor.set_effort(0.5)
uses a function provided for you in RosmoLib
called :code:set_effort
that is applied to the left motor. The :code:0.5
is a parameter to
this function which tells it that we'd like the motor to apply 50% effort.
On the Rosmo, we write percentages as decimal numbers between 0 and 1, with 1 being 100%.
.. Explain functions in greater depth later on (pinwheel activity?)
.. A function is a block of code that can be used multiple times in your program
.. to make complicated tasks easier. For example, the
.. :code:left_motor.set_effort()
function tells the left motor to apply an effort
.. you as the programmer specify.
.. :code:left_motor.set_effort
is a function that we provide for you in
.. RosmoLib. Later in the course you will see how you can write your own
.. functions to make it easy to make the robot do complicated sequences of actions.
.. When you want to use a function, you call it by writing its name in your code. .. This causes the function's code to start running.
.. The number you put between the parenthesis is a parameter (sometimes also .. called called an argument) of the function. These allow you to tell the .. function how it should do its job. As the programmer, you must provide a value .. for each parameter. If we wanted to make the robot drive forwards at full .. speed, we would call the function like this:
Now that we've tested the left motor, let's test the right one! How do you think
you would modify the code to spin the right motor? Simply replace
:code:left_motor
with :code:right_motor
.
.. admonition:: Try it out
Modify your code and run it on the robot. Make sure the right wheel spins.
Push an object like a pencil against the wheel to add some resistance.
Notice how the wheel slows down when you do this, since it would need more
effort to keep going the same speed.
We've gotten the wheels spinning forwards, but what if we want to go backwards? To do this, we simply have to pass in a negative number for the effort parameter. This means that we can use any number between -1 and 1 for the effort value. -1 will be full effort backwards, 1 will be full effort forwards, and 0 will stop the motor.
.. admonition:: Try it out
Try to write code that makes both wheels spin backwards.
This table shows some different effort values and what the wheel would do:
.. list-table:: :header-rows: 1
-
- Speed value
- Wheel action
-
- 1
- Wheel spins forwards at 100% effort
-
- 0.5
- Wheel spins forwards at 50% effort
-
- 0
- Wheel stops spinning
-
- -0.5
- Wheel spins backwards at 50% effort
-
- -1
- Wheel spins backwards at 100% effort
In the last lesson we learned how to set the effort of each of your robot's motors individually. Since both of the motors make up the robot's drivetrain, there's an easier way to write code to move the robot.
.. note::
For this lesson, put your Rosmo on a flat surface like a table or the floor.
Getting your Rosmo robot to move is simple! Here is some code you can use to drive both the left and right motors at 50% effort:
.. tab-set::
.. tab-item:: Python
.. code-block:: python
from RosmoLib.defaults import *
drivetrain.set_effort(0.5, 0.5)
.. tab-item:: Blockly
.. image:: 
.. tab-item:: Blockly
.. image:: , we can calculate how many seconds we should drive for to reach that distance.
Let's use :math:d
to represent the distance we want to drive in cm. But, we want
a number in seconds, so we need to convert by the means of dimensional analysis.
To do this, write an expression for the known value with units included:
.. math:: (d \text{ cm})
Dimensional analysis involves multiplying this expression by special representations
of "1" to convert units. In this case, our speed is 5 cm per second, so we can equate
:math:5 \text{ cm} = 1 \text{ second}
. Rearranging, we have our special representation of 1:
.. math::
\frac{1 \text{ second}}{5 \text{ cm}} = 1
We can now multiply our expression with this special representation of 1:
.. math:: (d \text{ cm}) \cdot \frac{1 \text{ second}}{5 \text{ cm}}
Cancelling out units and simplifying, we obtain:
.. math:: (d \cancel{\text{ cm}}) \cdot \frac{1 \text{ second}}{5 \cancel{\text{ cm}}} = \frac{d}{5} \text{ seconds}
This resultant expression makes sense! If we want to go 5 cm, we plug in d = 5, and :math:\frac{5}{5} = 1
,
so we drive for one second. If we want to go 2.5 cm, we plug in d = 2.5, and :math:\frac{2.5}{5} = 0.5
,
so we drive for half a second.
Keep in mind that this equation is only valid if the robot is moving at 5 cm per second. If you change that speed to be faster or slower, you'll need to change the denominator of the fraction to that speed to fix the equation.
.. admonition:: Try it out
Calculate how many seconds you need to drive for to go one meter if your
robot is moving at 5 cm per second. Remember, there are 100 cm in a meter.
To put the above theory into practice, we need to learn about a new function in Python:
:code:sleep
, which makes the Rosmo wait for some number of seconds before
continuing to the next instruction in the code.
.. tab-set::
.. tab-item:: Python
.. code-block:: python
from RosmoLib.defaults import *
from time import sleep # We need to import the speed function to use it.
drivetrain.set_speed(5, 5)
sleep(x) # replace x with the time you calculated to go one meter.
drivetrain.stop() # This is another function which makes it easy to stop the robot
.. tab-item:: Blockly
.. image:: 
sleep(25 / 5) # Notice how we can write math directly in our program!
drivetrain.stop()
# Drive 50 cm
drivetrain.set_speed(5, 5)
sleep(50 / 5)
drivetrain.stop()
# Drive 75 cm
drivetrain.set_speed(5, 5)
sleep(75 / 5)
drivetrain.stop()
This looks pretty repetitive. Most of this code is exactly the same. In fact,
the only change between each block is the parameter we are passing to the
:code:sleep
function. This is a perfect example of why we have functions.
Let's write our own function to drive the robot a certain distance.
.. tab-set::
.. tab-item:: Python
Python uses the keyword :code:`def` to let you, the programmer, tell it that you
would like to *define* a new function. A full function definition looks like
this:
.. code-block:: python
def function_name(parameter1, parameter2, parameter3):
# put your code here
# code in your function can use the parameters by name like this:
print(parameter1 / 5)
In this example function, there are three parameters. Functions can have as
many or as few parameters as you want, or even have no parameters at all.
.. tab-item:: Blockly
In Blockly, you create functions by dragging a block that looks like the picture
below. The interface allows you to specify the function name, and pass *parameters*
to the function body. Here, we have a function called some_task (which you should rename
based on what your function does) that takes in a parameter called :code:`text`, and uses
prints the :code:`text` value. Functions can have as many or as few parameters as you want,
or even have no parameters at all.
.. image::  has rotated. We mentioned that our motors aren't perfect, so when we tell them to go a certain effort we don't know how fast it is actually rotating. Encoders measure exactly what the motor is doing and report this information back to the Rosmo.
.. image:: )
sleep(1)
.. tab-item:: Blockly
.. image::  which would be too much data for your computer to display at once on the screen.
.. admonition:: Try it out
Try running this code to see what happens. Spin the left wheel of the Rosmo
by hand and notice how the number changes.
Let's learn a bit about how the Rosmo uses the encoder to calculate how far the robot has moved.
The Rosmo knows the diameter of the robot's wheels; every Rosmo has the same wheels!
If a car's wheel rolls on the ground one full revolution, how far does the car move? The car moves by one circumference of the circle:
.. image::  rotates 5 times, how far does the car go? How would you find that?
.. image:: , if you rotated it 1 and a half times, you would move forward one and a half times the circumference (150 inches).
.. math::
d\text{ cm} = (\text{number of rotations}) \cdot (\text{circumference})
The Rosmo uses this equation to automatically calculate how far the wheels have moved in centimeters using the encoders.
In the last lesson you used a constant speed and a time to drive a distance.
Now that you know that the encoders actually measure the distance, it would be
better to use them for your :code:drive_distance
function. We can modify your
function to use a :code:While
loop:
.. tab-set::
.. tab-item:: Python
.. code-block:: python
def drive_distance(distance_to_drive):
while drivetrain.get_left_encoder_position() < distance_to_drive:
drivetrain.set_speed(5, 5)
time.sleep(0.01)
drivetrain.stop()
.. tab-item:: Blockly
.. image:: `
Once you know how to drive a certain distance with the Rosmo, it is easy to turn to a certain heading with it. First, you need to calculate the distance that a wheel must travel so that you are facing the correct heading, and then simply rotate the Rosmo until the encoders have traveled that distance.
Calculating the necessary distance is complicated, but we can break down this problem into steps.
first, lets make a fraction that represents from 0.0 to 1.0 how far around the robot's circumference the wheels need to travel. In this case 0.0 is 0 degrees and 1.0 is 360 degrees.
Now to get the distance the wheel travels, we need to multiply this fraction by the total distance the wheel travels to rotate 360 degrees, this number is the circumference of a circle with the diameter same diameter as the robot. We can calculare this by multiplying the wheel track distance by pi.
finally, to get the number of wheel rotations, we need to divide this distance by the circumference of the wheel, or pi times the wheel diameter. We can cancel pi from both sides of this division and that leaves us with.
.. math::
\frac{\text{target degrees} \cdot \text{robot wheel track}} {360 \cdot \text{wheel diameter}}
Now that we have the number of wheel rotations, the rest of the program is easy. just turn the robot in the direction of the turn, and stop once the number of rotations has exceeded the calculated rotation goal.
.. tab-set:: .. tab-item:: Python .. code-block:: python def turn(target): global rotations differentialDrive.reset_encoder_position() rotations = (target * 15.5) / (360 * 6) if target > 0: differentialDrive.set_effort((-0.3), 0.3) else: differentialDrive.set_effort(0.3, (-0.3)) while not math.fabs(motor1.get_position()) >= math.fabs(rotations):
differentialDrive.stop()
.. tab-item:: Blockly
.. image:: 
.. tab-item:: Blockly
.. image:: 
will make the
robot go straight 20 centimeters, and use the default values for everything
else, meaning a maximum effort applied of 50% and no timeout.
You can also use a negative value for distance to drive backwards.
The :code:max_effort
parameter specifies how much effort the robot is allowed
to apply while driving. By default it is 50%, which is a good effort for normal
driving on a flat surface.
The :code:timeout
parameter specifies a time, in seconds, that the robot
should try to drive before giving up. For example, what if your robot runs into
something while driving, and the wheels get stuck? The robot will use the
encoders to measure the wheels and notice that it never arrived at the distance
you set, so it will try forever and none of your code will run afterwards. The
timeout lets you set a maximum time that the Rosmo should try for before giving
up. Usually, you won't need to use this, but it is there if you need it.
.. tab-set::
.. tab-item:: Python
.. code-block:: python
drivetrain.turn(90, max_effort = 0.5, timeout = None)
.. tab-item:: Blockly
.. image:: 
differentialDrive.turn(120, 0.5)
differentialDrive.straight(30, 0.5)
differentialDrive.turn(120, 0.5)
differentialDrive.straight(30, 0.5)
.. tab-item:: Blockly
.. image:: 
drivetrain.turn(90, 0.5)
drivetrain.straight(30, 0.5)
drivetrain.turn(90, 0.5)
drivetrain.straight(30, 0.5)
drivetrain.turn(90, 0.5)
drivetrain.straight(30, 0.5)
.. tab-item:: Blockly
.. image:: :
for i in range(int(numSides)):
differentialDrive.turn((360 / numSides), 0.5)
differentialDrive.straight(sideLength, 0.5)
.. tab-item:: Blockly
.. image:: :
for i in range(numShapes):
polygon(sideLength, numSides)
drivetrain.turn(360 / numShapes, 0.5)
.. tab-item:: Blockly
.. image:: 
- Red electrical tape or red marker
- Scissors (or cutting tool)
- Ruler (or straightedge)
.. figure:: ![](https://raw.githubusercontent.com/Open-STEM/IntroToRoboticsV2/main/course/driving/media/labyrinth.png :width: 300 :align: center
Carnegie Mellon Robotics Academy, Labyrinth Challenge
The robot should do the following:
- Begin fully contained in position 1 and then maneuver to goal area.
- Reach and be fully contained within position 2 without crossing any black lines.