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

Материал из roboforum.ru Wiki
Версия от 14:24, 18 мая 2009; =DeaD= (обсуждение | вклад) (переименовал «Программирование LEGO NXT роботов на языке NXC - Задачи и процедуры» в «[[Программирование LEGO NXT роботов на языке NXC - Задачи и фун)
Перейти к: навигация, поиск

Автор: 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-й главе.

Функции

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

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

В этой программе мы объявили функцию, которая разворачивает робота на месте. Главная задача вызывает эту функцию три раза. Обратите внимание, что мы вызываем функцию, указывая в круглых скобках числовой аргумент. Если у функции нет параметров - нужно будет просто указывать пустые скобки.

Так что использование функции выглядит также, как и использование многих других команд, которые вы уже видели.

Основное преимущество функций в том, что они хранятся в NXT только раз и таким образом экономится память. Но когда функции короткие, может быть лучше использовать "inline"-функции. Они не хранятся отдельно, а копия их размещается в каждом месте, откуда вызывается функция. Это использует больше памяти, но на количество таких функций ограничения нет. Такие функции могут быть объявлены следующим образом:

inline int Name( Args ) {
  //body;
  return x*y;
}

Определение и вызов "inline" функций делается так же как и обычных. Смотрите пример использования этих функций ниже:

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

В указанном выше примере, мы можем сделать время для разворота аргументом функции, как показано тут:

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

Заметим, что при определении в скобках после имени функции мы указываем аргументы этой функции. В этом случае мы показываем, что аргумент функции - целое число (можно выбрать другие типы), а имя этой переменной - "turntime". Когда у функции больше аргументов, вы должны разделять их запятыми. Обратите внимание, что в NXC, ключевое "sub" является синонимом "void"; также, функции могут иметь другой тип, отличный от "void", они могут возвращать целые числа, строковые значения вызывающему, для подробной информации на эту тему смотрите полную инструкцию по языку NXC.

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

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.

#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.

#define turn_right(s,t) \
OnFwd(OUT_A, s);OnRev(OUT_B, s);Wait(t);
#define turn_left(s,t) \
OnRev(OUT_A, s);OnFwd(OUT_B, s);Wait(t);
#define forwards(s,t) OnFwd(OUT_AB, s);Wait(t);
#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.