VPL Lab 2 - Advanced Motion
Glossary Item Box
VPL Hands On Labs: VPL Lab 3 - DriveDistance and RotateDegrees
VPL User Guide: Getting Started
Microsoft Robotics Developer Studio | Send feedback on this topic |
VPL Lab 2 - Advanced Motion
In this task, you will learn about driving a differential drive robot in arcs, and splicing those arcs together to make more complicated trajectories.
This lab will teach you more advanced concepts in VPL, such as variables, initial values, timers, and concurrent flow control via merges and joins. In addition, you will learn about arc trajectories for a differential drive robot and the pitfalls of open-loop control.
This lab is provided in the language. You can find the project files for this lab at the following location under the Microsoft Robotics Developer Studio installation folder:
Samples\VplHandsOnLabs\Lab2
This lab teaches you how to:
- Drive in Circles
- Drive along an Arc
- Drive in a Figure-of-8
- Drive in Alternating Spirals
See Also:
- Getting Started
Prerequisites
Hardware
You can use any differential drive robot, either simulated or physical. However, the Lab is set up by default to use a simulated robot.
Software
This lab is designed for use with Microsoft Visual Programming Language (VPL). You will also need Microsoft Internet Explorer or another conventional web browser.
Getting Started
To get started, open the Visual Programming Language application.
Step 1: Drive in Circles
The first programming task is simple: making the robot drive in circles. Before doing that, you need to decide what power to apply to the wheels. If the power is not the same on each wheel, the robot will drive in an arc. If you drive the robot for long enough the arc will become a circle.
Calculate Drive Power
Start by adding a custom activity called TurningRadiusToWheelPowers to your diagram. (Adding a custom activity is explained in the VPL Tutorial called "Create Your Own Actitivity", but brief instructions are also included here). Drag an Activity block from the toolbox onto the diagram. In the Properties toolbox, change the name of the activity to TurningRadiusToWheelPowers. Then double-click on the custom activity block itself to open it in the editor.
Inside the custom activity, you will see that there is an action called Action. This is not a very meaninful name. Select Actions and Notifications from the Edit menu and change the name of the action to CalculateWheelPowers. While this dialog is open, add two input values called TurningRadius (a double) and TurnRight (a boolean). Also add two output values called Left (a double) and Right (also a double). You can see all of these changes in the following figure.
Figure 1 - Actions and Notifications dialog
Add two Calculate activities and a Data activity as shown in the next figure, and connect them to the input pin which is on the left-hand border of the action diagram. The first Calculate simply extracts the TurnRight value from the incoming message so that it can be added to the Join. The Data block sets the power for the left wheel to 1.0. The second Calculate does the real calculations which are intended to figure out an appropriate value for the right wheel power.
Figure 2 - Calculate Drive Powers
All three values are then combined using a Join activity. This activity waits until it receives an input on each pin and then constructs a single output message containing all of the values. You must set the names for each of the data members in the textboxes on the Join as shown (TurnRight, Left and Right).
Now that you have the required results, you need to output them from the action. This action is designed to generate the wheel powers for either a left-hand or right-hand turn. Add an If activity to check the value of TurnRight. Connect the input of the If to the output of the Join. If TurnRight is true, then the left and right values can be used directly. So Calculate blocks are used to extract these values and pass them to another Join. However, if TurnRight is false then the values are swapped. This is very clear from the diagram because you can see the connections crossing over.
Finally, the output of the Join blocks must be connected to the result pin. Only one of these will be activated (due to the preceeding If), so you can use a Merge to combine the dataflows and connect the output of the Merge to the result pin on the right-hand border of the action diagram. See the figure below.
Figure 3 - Send Drive Powers as Result Output
This completes the TurningRadiusToWheelPowers action. You can close the action by clicking on the 'X' beside its name in the tab at the top of the window. Alternatively, just click on the tab that says Diagram to return to the main diagram.
Add Generic Differential Drive
Add a Generic Differential Drive service to your diagram. Connect the output port of the TurningRadiusToWheelPowers to the input port of the Generic Differential Drive (associating Left with LeftWheelPower and Right with RightWheelPower). TurningRadiusToWheelPower takes in two arguments: TurnRight, which determines whether or not to turn to the right (otherwise, turn to the left), and the TurningRadius (a double) and a boolean.
Drag over two Data blocks from the Basic Activities toolbox. A Data block specifies raw data (also called a literal). Set one block to be of type boolean with value true, and the other to be of type double with value 1.0. These will be the inputs to TurningRadiusToWheelPower.
Services only allow one incoming message type per connection, since a connection corresponds to an execution path. (However, a service allows multiple outgoing connections, because a single path might spawn multiple concurrent tasks). To aggregate the two pieces of data, you must add a Join block from Basic Activies. This will create a compound message out of the two values, allowing you to access the values by the names you specify. Connect the boolean data to the Join, specifying the name TurnRight; also connect the double data to the Join, calling it TurningRadius. The output of the Join can then be connected to the input of TurningRadiusToWheelPowers.
A couple of important notes about Join:
- You can aggregate as many values as you want with a Join - simply attach a new link to the corner and a new name box will appear.
- A Join waits until data is ready on all incoming pins. As far as execution is concerned, it operates like an AND.
Figure 4 - VPL diagram to go in circles
Your final diagram should resemble Figure 4, and it is ready for initial testing. Building small pieces of code and testing them as you go is a good idea.
Before you run this program to drive the robot, you should consider the safety aspects. If you simply run the program as in Figure 4, then the robot will start up immediately when the program starts. This might take somebody by surprise and you do not want your robot crashing into people because it might hurt them or itself. Therefore you should have a "start button" so that you can tell it when to begin moving. If you are using a simulated robot then this is not a safety issue, but it is still good practise because the simulator takes a little while to start up. |
Add a Data block and a SimpleDialog to your diagram as shown in Figure 5. Notice that the data type in the Data block is set to string and it contains the text "Press OK when the robot is ready". (You do not need to put the quotation marks around the text when you type it in). The output of the Data block goes to the input of the Simple Dialog, which you can drag from the Services toolbox. Hook up the Data block to use the AlertDialog action of the Simple Dialog and in Data Connections connect the value to the AlertText. Drag a connection from the output of the Simple Dialog to the input of the Data block for the boolean value that sets the TurnRight value.
Figure 5 - Code to delay startup
The effect of these extra activities is to delay sending the request to the robot's drive system until you have clicked on the OK button in the Simple Dialog window. This pauses the program until you are sure that nobody is in the path of the robot. Another useful feature is that it usually takes a little bit of time for the program to start up and connect to the robot. (In the case of the iRobot Create you will hear a couple of tones from the robot when the connection is established). If you try to send a command before the robot is ready, in this case to the Generic Differential Drive service, then the request might be lost or cause an error. Trying to debug this sort of "race condition" is very difficult.
As in Lab 1, specify a manifest for the GenericDifferentialDrive and test out your program. You can chose a manifest for a simulated robot or a real robot. Run the program several times and vary the data values to verify that they affect the robot's behavior, i.e. the direction of the turn and the radius of the circle. The robot will drive in a circle until its batteries run out or you catch it and turn it off.
Step 2: Drive along an Arc
In Step 1, the robot continues driving in circles because its motor powers are still set. To drive along an arc, you must stop the motors after some period of time, i.e. set the powers to zero.
For this purpose, you need to use the Timer service. Add one to your diagram and connect its input to the output of the Generic Differential Drive (associating the SetDrivePowerSuccess result with the SetTimer request). In the Data Connections dialog, check Edit Values Directly and type in 1500 for the value (the units are milliseconds). This allows you to specify an input value for the Timer even though Generic Differential Drive has no output. Instead, this link only indicates execution flow. (You could have accomplished the same thing with a Data block, but this is a useful shortcut that keeps your diagram from growing too large).
Figure 6 - Edit values directly
Now you need tell the robot to stop when the Timer is finished. Drag in a new Timer, and in the Add Activity dialog that pops up, select the option to use the Timer that already exists instead of adding a new one. The two Timer blocks on the diagram will then reference the same timer. Connect the notification port (red circle) on the new Timer to a new Data block with a double value of 0.0. Use the TimerComplete operation for this connection. This will set up the execution path so that when the timer expires, execution will begin here (concurrently with any other paths being executed). Although you could have used a loop instead of these two timer blocks, this way is neater and more cleanly follows the asynchronous programming paradigm (i.e. it is non-blocking).
In order to stop the robot, you must set both the left and right wheels to zero power. Add a Join with names Left and Right and attach the output of the Data block to both of them. Drag in a new Generic Differential Drive. Again, select the existing service instead of adding a new one. Connect the output of the Join to the input of the new Generic Differential Drive and set the LeftWheelPower to Left and the RightWheelPower to Right.
Figure 7 - VPL diagram to drive along an arc
The bottom dataflow in the diagram appears to be completely disconnected. However, the Timer at the start of the flow is the same Timer at the end of the flow above it. Although it might not be apparent to you yet, you can tell that these are both the same activity because they both have a name of Timer. If you had inadvertently created a new activity when you added the second block, it would have a name of Timer0. All service activities must have unique names. You can change the name of a service activity by selecting it and changing the name in the Properties toolbox. Try this with the Timer and see what happens. The new name should appear in both service activity blocks. |
Run the program. Your robot should drive along an arc and stop. Again, vary the hard-coded values and watch its behavior.
Step 3: Drive in a Figure-of-8
Now try something a bit more challenging: switching the direction of the circle halfway through to make a Figure-of-8. Begin by deleting the dataflow you just added to stop the robot.
Now add a Variable to your program. In VPL, a Variable is a named value that has global scope (you can Get or Set it from anywhere in the diagram). The set of all variables represents the state of your application. You can access the same variables from different dataflows in the same diagram, thus sharing information. (Note that setting the value of a variable can affect how dataflows are scheduled. You will learn more about this in a later lab.)
Drag in a new Variable block from Basic Activities. Click on the ellipsis (...) on the Variable block to bring up the Define Variables dialog.
Figure 8 - Defining variables
Add a new boolean variable called TurnRight and hit OK. This variable will determine whether the robot turns to the right or to the left. Change the booleanData block's output to connect to the input of your new Variable. Choose the Set Value action. You now have an initialized variable.
Figure 9 - Set a Variable
Change the wait time of the Timer to 5000 milliseconds, approximately the amount of time for the Create to complete a circle of radius 1 meter. You might have to experiment with this value to get it exactly right for your robot. The value is also likely to change as the batteries on the robot run down.
A Variable would not be very useful if you Set it but never used it. You can get the value of a variable by using another Variable block, selecting the same variable name, and choosing the Get action. The output of the Variable block is a name-value pair, so in order to get the value, you would have to add a Calculate block and type the variable name in it. An easier way to get a variable value is the state keyword. You can use it wherever you can enter an expression. Add a Calculate block and connect it to the output of the Timer. Enter the expression state.TurnRight. The output of the calculate will now be the value of the TurnRight variable.
In order to toggle this value, change the expression to !state.TurnRight. The exclamation mark (!) is the logical NOT operator, i.e. if the value is true, make it false and vice versa.
For now, this is only a value "on the wire". Next, you must Set the TurnRight variable to this new value. Add another Variable activity and select the TurnRight variable in the drop-down list. Connect from the output of the Calculate to the Variable block and choose the Set action. Connect from the same Calculate to the TurnRight field of the Join. You have to delete the Data activity that is connected to it, before you do that.
There is one detail you must address: the Join block waits until both its inputs are available on the wire. As drawn right now, the diagram will send the TurningRadius data to Join only once, at startup. You want it to be sent each time through this dataflow. Thus, you must add a connection between the output of the TimerComplete and the input of the Data block with the double value.
If you ran the program like this, nothing would happen. The timer is never started. You have to make sure that the SetTimer message is sent to the Timer service to get things going. Also, you want to make sure that the you start driving as soon as you click on "OK" and not wait until the first timer has completed. Copy the Join and the Data block with the double value to where you already initialized the TurnRight variable. Connect the Data block with the boolean to the Join. Finally, connect the output of the Join to the CalculateWheelPowers of TurningRadiusToWheelPowers. This activity is already connected, so you have to add a merge into that connection.
Figure 10 - VPL diagram to drive in a Figure-of-8
Above is the completed diagram to drive in a Figure-of-8. Try it out! If it does not make a complete circle before changing directions, you will have to adjust the timer delay.
Step 4: Drive in Alternating Spirals
To drive in alternating spirals, all you need to do is alter the turning radius on every cycle through the main code path. To accomplish this modification, add another Variable called TurningRadius, which you will Get and Set just as you did with TurnRight. However, as well as switching the boolean value of TurnRight, you will need to multiply the double value of TurningRadius by some number greater than one (say 1.1 for example).
Figure 11 - VPL diagram to drive in alternating, growing spirals
The completed diagram above drives the robot in alternating spirals. Try it out! You might notice that the robot does not drive back to the same center point each time. The reason is the hard-coded delay for the turning time. If you are keen, you can fix this issue by adding another Variable for the turning time and increasing it at the same rate as you increase the turning radius (since the two are proportional). This paradigm for determining where you are (by pure computation, not by sensing) is called dead-reckoning or open-loop control. Already, you should be able see the benefits of sensors to figure out how you are moving and/or where you are.
Summary
In this lab, you learned how to:
- Drive in Circles
- Drive along an Arc
- Drive in a Figure-of-8
- Drive in Alternating Spirals
See Also |
VPL Hands On Labs: VPL Lab 3 - DriveDistance and RotateDegrees
VPL User Guide: Getting Started
© 2012 Microsoft Corporation. All Rights Reserved.