Программирование LEGO NXT роботов на языке NXC - Задачи и функции

Материал из roboforum.ru Wiki
Версия от 15:17, 17 мая 2009; =DeaD= (обсуждение | вклад) (Создана новая страница размером <p align=center><b>Автор: Daniele Benedettelli</b><br><br><i>Перевод: © Ботов Антон aka =DeaD=, 2009<br><br>Экс...)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Автор: Daniele Benedettelli

Перевод: © Ботов Антон aka =DeaD=, 2009

Эксклюзивно для www.roboforum.ru
копирование на другие ресурсы и публикация перевода
без разрешения его автора запрещены

Задачи и процедуры

Up to now all our programs consisted of just one task. But NXC programs can have multiple tasks. It is also possible to put pieces of code in so-called subroutines that you can use in different places in your program. Using tasks and subroutines makes your programs easier to understand and more compact. In this chapter we will look at the various possibilities.

Задачи

An NXC program consists of 255 tasks at most; each of them has a unique name. The task named main must always exist, since this is the first task to be executed. The other tasks will be executed only when a running task tells them to be executed or they are explicitly scheduled in the main; main task must terminate before they can start. From that moment on, both tasks are running simultaneously. Let me show the use of tasks. We want to make a program in which the robot drives around in squares, like before. But when it hits an obstacle it should react to it. It is difficult to do this in one task, because the robot must do two things at the same moment: drive around (that is, switching motors on and off in time) and watch for sensors. So it is better to use two tasks for this, one task that moves in squares; the other that reacts to the sensors. Here is the program. mutex moveMutex; task move_square() { while (true) { Acquire(moveMutex); OnFwd(OUT_AC, 75); Wait(1000); OnRev(OUT_C, 75); Wait(500); Release(moveMutex); } } task check_sensors() { while (true) { if (SENSOR_1 == 1) { Acquire(moveMutex); OnRev(OUT_AC, 75); Wait(500); OnFwd(OUT_A, 75); Wait(500); Release(moveMutex); } } } task main() { Precedes(move_square, check_sensors); SetSensorTouch(IN_1); } The main task just sets the sensor type and then starts both other tasks, adding them in the scheduler queue; after this, main task ends. Task move_square moves the robot forever in squares. Task check_sensors checks whether the touch sensor is pushed and, if so, drives the robot away from obstacle. It is very important to remember that started tasks are running at the same moment and this can lead to unexpected results, if both tasks are trying to move motors as they are meant to do.

To avoid these problems, we declared a strange type of variable, mutex (that stands for mutual exclusion): we can act on this kind of variables only with the Acquire and Release functions, writing critical pieces of code between these functions, assuring that only one task at a time can have total control on motors. These mutex-type variables are called semaphores and this programming technique is named concurrent programming; this argument is described on detail in chapter X.

Процедуры

Sometimes you need the same piece of code at multiple places in your program. In this case you can put the piece of code in a subroutine and give it a name. Now you can execute this piece of code by simply calling its name from within a task. Let us look at an example. sub turn_around(int pwr) { OnRev(OUT_C, pwr); Wait(900); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(75); Wait(2000); turn_around(75); Wait(1000); turn_around(75); Off(OUT_AC); } In this program we have defined a subroutine that makes the robot rotate around its center. The main task calls the subroutine three times. Note that we call the subroutine by writing its name and passing a numerical argument writing it inside following parentheses. If a subroutine accepts no arguments, simply add parentheses with nothing inside them. So it looks the same as many of the commands we have seen. The main benefit of subroutines is that they are stored only once in the NXT and this saves memory. But when subroutines are short, it may be better to use inline functions instead. These are not stored separately but copied at each place they are used. This uses more memory but there is no limit on the number of inline functions. They can be declared as follows: inline int Name( Args ) { //body; return x*y; } Defining and calling inline functions goes exactly the same way as with subroutines. So the above example, using inline functions, looks as follows:

inline void turn_around() { OnRev(OUT_C, 75); Wait(900); OnFwd(OUT_AC, 75); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(); Wait(2000); turn_around(); Wait(1000); turn_around(); Off(OUT_AC); } In the above example, we can make the time to turn an argument of the function, as in the following examples: inline void turn_around(int pwr, int turntime) { OnRev(OUT_C, pwr); Wait(turntime); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); turn_around(75, 2000); Wait(2000); turn_around(75, 500); Wait(1000); turn_around(75, 3000); Off(OUT_AC); } Note that in the parenthesis behind the name of the inline function we specify the argument(s) of the function. In this case we indicate that the argument is an integer (there are some other choices) and that its name is turntime. When there are more arguments, you must separate them with commas. Note that in NXC, sub is the same as void; Also, functions can have other return type than void, can also return integer or string values to the caller: for details, see the NXC guide.

Определяем макрос

There is yet another way to give small pieces of code a name. You can define macros in NXC (not to be confused with the macros in BricxCC). We have seen before that we can define constants, using #define, by giving them a name. But actually we can define any piece of code. Here is the same program again but now using a macro for turning around.

  1. define turn_around \

OnRev(OUT_B, 75); Wait(3400);OnFwd(OUT_AB, 75); task main() { OnFwd(OUT_AB, 75); Wait(1000); turn_around; Wait(2000); turn_around; Wait(1000); turn_around; Off(OUT_AB); } After the #define statement the word turn_around stands for the text behind it. Now wherever you type turn_around, this is replaced by this text. Note that the text should be on one line. (Actually there are ways of putting a #define statement on multiple lines, but this is not recommended.) Define statements are actually a lot more powerful. They can also have arguments. For example, we can put the time to turn as an argument in the statement. Here is an example in which we define four macro's; one to move forwards, one to move backwards, one to turn left and one to turn right. Each has two arguments: the speed and the time.

  1. define turn_right(s,t) \

OnFwd(OUT_A, s);OnRev(OUT_B, s);Wait(t);

  1. define turn_left(s,t) \

OnRev(OUT_A, s);OnFwd(OUT_B, s);Wait(t);

  1. define forwards(s,t) OnFwd(OUT_AB, s);Wait(t);
  2. define backwards(s,t) OnRev(OUT_AB, s);Wait(t);

task main() { backwards(50,10000); forwards(50,10000); turn_left(75,750); forwards(75,1000); backwards(75,2000); forwards(75,1000); turn_right(75,750); forwards(30,2000); Off(OUT_AB); } It is very useful to define such macros. It makes your code more compact and readable. Also, you can more easily change your code when you e.g. change the connections of the motors.

Подводим итоги

In this chapter you saw the use of tasks, subroutines, inline functions, and macros. They have different uses. Tasks normally run at the same moment and take care of different things that have to be done at the same moment. Subroutines are useful when larger pieces of code must be used at different places in the same task. Inline functions are useful when pieces of code must be used a many different places in different tasks, but they use more memory. Finally macros are very useful for small pieces of code that must be used a different places. They can also have parameters, making them even more useful.

Now that you have worked through the chapters up to here, you have all the skills you need to make your robot do complicated things. The other chapters in this tutorial teach you about other things that are only important in certain applications.