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

Материал из roboforum.ru Wiki
Перейти к: навигация, поиск

Автор: Daniele Benedettelli

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

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

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

До этого момента все наши программы состояли из просто одной задачи. Но программы на языке NXC могут иметь множественные задачи. Также возможно размещать куски программы в так называемых процедурах, которые вы можете использовать в различных местах своей программы. Использование задач и процедур делает вашу программу более лёгкой для понимания и более компактной. В этой главе мы рассмотрим эти возможности.

Задачи

Каждая программа NXC может состоять максимум из 255 задач; каждая из которых имеет уникальное имя. Задача с именем "main" всегда должна присутствовать в любой программе, так как это первая задача которая отправляется на выполнение. Другие задачи будут выполняться только если уже запущенная на выполнение задача скажет системе чтобы она их выполнила, или они могут быть неявно запланированы к выполнению; главная задача должна завершиться в этом случае перед тем, как будут выполнены такие задачи. После её завершения запланированные задачи будут выполняться параллельно.

Давайте я покажу вам использование задач на следующем примере. Мы хотим сделать программу по которой робот ездит по квадрату, как и раньше, но если он встречает препятствие - он должен на него среагировать. Это тяжело реализовать в одной задаче, так как робот должен делать две вещи в один и тот же момент - ездить по нужной траектории (т.е. включать\выключать моторы в нужные моменты времени) и следить за сенсорами. Так что лучше мы будем использовать для этого две задачи, первая из которых будет ездить по квадрату, а вторая реагировать на показания датчиков. Вот текст этой программы:

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);
}

Главная задача просто объявляет какой сенсор куда подключен и после этого запускает две другие задачи, планируя их к выполнению, после чего завершает свою работу. Задача "move_square" бесконечно перемещает робота по квадрату. Задача "check_sensors" проверяет, сработал ли датчик касания и, если это так, отводит робота от препятствия.

Очень важно помнить, что будучи запущенными задачи работают одновременно, что может привести к непредсказуемым результатам, если задачи одновременно попытаются получить управление моторами робота.

Чтобы избежать таких проблем, мы объявили странный тип переменной, "mutex" (в русском языке этому слову соответствует "мьютекс", это сокращение от "mutual exclusion", т.е. взаимное исключение): мы можем взаимодействовать с таким типом переменных только с помощью функций Acquire и Release, записывая критические куски кода между этими функциями, при этом оставаясь уверенными, что только одна задача в один момент времени имеет полное управление над моторами робота.

Такие "мьютекс"-переменные еще называются семафорами, а такая техника программирования называется конкурентной. Более подробно это обсуждается в 10-й главе.

Процедуры

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.