#2 Продвинутое движение
В этом задании вы узнаете, как вести двухколёсного робота по дуге и объединять такие дуги вместе для задания более сложной траектории.
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.
Это лабораторная научит вас:
You can use any differential drive robot, either simulated or physical. However, the Lab is set up by default to use a simulated robot.
This lab is designed for use with Microsoft Visual Programming Language (VPL). You will also need Microsoft Internet Explorer or another conventional web browser.
Приступая к работе
Для начало работы откройте редактор Visual Programming Language.
Шаг 1: движение по окружности
Первое задание простое - заставить робота двигаться по окружности. Перед тем, как сделать это, необходимо решить, какую мощность передавать на двигатели колёс. Если мощность не одинакова на каждом из колёс, то робот будет двигаться по дуге. Траектория такого движения со временем замкнётся в окружность.
Расчёт мощности двигателей
Создайте новую собственную активность и назовите её TurningRadiusToWheelPowers и добавьте на диаграму. (Adding a custom activity is explained in the VPL Tutorial called "Create Your Own Actitivity", but brief instructions are also included here). Перенесите блок Activity из панели инструментов на диаграмму.В панели Properties смените название активности на TurningRadiusToWheelPowers. Двойной клик на блоке откроет его в редакторе.
Inside the custom activity, you will see that there is an action called Action. Это не очень понятное название. Выберите пункт Actions and Notifications из меню Edit и смените название действия на CalculateWheelPowers. Пока диалоговое окно открыто добавьте 2 входных значения и назовите их TurningRadius (десятичное значение) и TurnRight (булевое значение). Также добавьте два выходных значения и назовите их Left (десятичное значение) и Right (тоже десятичное значение). Все изменения можно увидеь на рисунке.
Добавьте два блока Calculate и блок Data, как показано на следующем рисунке. and connect them to the input pin which is on the left-hand border of the action diagram. Первый блок Calculate просто извлекает значение TurnRight из входящего сообщения, чтобы передать его в блок Join. Блок Data устанавливает значение мощности двигателя левого колеса равным 1.0. Второй блокCalculate выполняет расчёты, необходимые для получения подходящих значений мощности для двигателя правого колеса..
Затем все три значения соединяются при помощи блока Join. Этот блок ждёт получения входных данных на каждом из разъёмов и затем строит единое выходное сообщение, содержащее все эти значения. Вы должны ввести в текстовые поля блока Join имена каждого элемента в образовавшемся массиве данных, как показано на рисунке (TurnRight, Left и 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.
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 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.
- 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.
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.
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.
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).
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.
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.
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.)
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.
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.
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.
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).
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.
Комментарии и вопросы к документу
Этот документ ещё никто не прокомментировал :)
Вы можете оставить свой комментарий или вопрос.