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

Материал из roboforum.ru Wiki
Перейти к: навигация, поиск
(Процедуры)
(Подводим итоги)
 
(не показано 11 промежуточных версий этого же участника)
Строка 1: Строка 1:
 
<p align=center><b>Автор: Daniele Benedettelli</b><br><br><i>Перевод: © Ботов Антон aka =DeaD=, 2009<br><br>Эксклюзивно для www.roboforum.ru<br> копирование на другие ресурсы и публикация перевода<br>без разрешения его автора запрещены</i></p>
 
<p align=center><b>Автор: Daniele Benedettelli</b><br><br><i>Перевод: © Ботов Антон aka =DeaD=, 2009<br><br>Эксклюзивно для www.roboforum.ru<br> копирование на другие ресурсы и публикация перевода<br>без разрешения его автора запрещены</i></p>
  
=Задачи и процедуры=
+
=Задачи и функции=
До этого момента все наши программы состояли из просто одной задачи. Но программы на языке NXC могут иметь множественные задачи. Также возможно размещать куски программы в так называемых процедурах, которые вы можете использовать в различных местах своей программы. Использование задач и процедур делает вашу программу более лёгкой для понимания и более компактной. В этой главе мы рассмотрим эти возможности.
+
До этого момента все наши программы состояли из просто одной задачи. Но программы на языке NXC могут иметь множественные задачи. Также возможно размещать куски программы в так называемых функциях, которые вы можете использовать в различных местах своей программы. Использование задач и функций делает вашу программу более лёгкой для понимания и более компактной. В этой главе мы рассмотрим эти возможности.
  
 
==Задачи==
 
==Задачи==
Строка 49: Строка 49:
 
Такие "мьютекс"-переменные еще называются семафорами, а такая техника программирования называется конкурентной. Более подробно это обсуждается в 10-й главе.
 
Такие "мьютекс"-переменные еще называются семафорами, а такая техника программирования называется конкурентной. Более подробно это обсуждается в 10-й главе.
  
==Процедуры==
+
==Функции==
Иногда вам нужно один и тот же кусок программы использовать в разных местах. В этом случае вы можете поместить этот кусок программы в процедуру и дать ей имя. Тогда вы сможете вызывать этот кусок программы на выполнение просто вызывая это имя из задачи. Давайте взглянем на следующий пример.
+
Иногда вам нужно один и тот же кусок программы использовать в разных местах. В этом случае вы можете поместить этот кусок программы в функцию и дать ей имя. Тогда вы сможете вызывать этот кусок программы на выполнение просто вызывая это имя из задачи. Давайте взглянем на следующий пример.
  
 
  sub turn_around(int pwr)
 
  sub turn_around(int pwr)
Строка 70: Строка 70:
 
  }
 
  }
  
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
+
Основное преимущество функций в том, что они хранятся в NXT только раз и таким образом экономится память. Но когда функции короткие, может быть лучше использовать встроенные ("inline") функции. Они не хранятся отдельно, а копия их размещается в каждом месте, откуда вызывается функция. Это использует больше памяти, но на количество таких функций ограничения нет. Такие функции могут быть объявлены следующим образом:
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 ) {
 
  inline int Name( Args ) {
Строка 87: Строка 81:
 
  }
 
  }
  
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()
 
  inline void turn_around()
Строка 108: Строка 101:
 
  }
 
  }
  
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)
 
  inline void turn_around(int pwr, int turntime)
Строка 129: Строка 122:
 
  }
 
  }
  
Note that in the parenthesis behind the name of the inline function we specify the argument(s) of the function. In
+
Заметим, что при определении в скобках после имени функции мы указываем аргументы этой функции. В этом случае мы показываем, что аргумент функции - целое число (можно выбрать другие типы), а имя этой переменной - "turntime".
this case we indicate that the argument is an integer (there are some other choices) and that its name is turntime.
+
Когда у функции больше аргументов, вы должны разделять их запятыми. Обратите внимание, что в NXC, ключевое "sub" является синонимом "void"; также, функции могут иметь другой тип, отличный от "void", они могут возвращать целые числа, строковые значения вызывающему, для подробной информации на эту тему смотрите полную инструкцию по языку NXC.
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
+
Существует еще один способ дать небольшому фрагменту кода собственное имя для обращения. В языке NXC вы можете определить макрос ( не путайте с макросами BricxCC). Мы видели ранее, что можем определять константы используя ключевое слово "#define", назначая им имена. Но на самом деле мы можем таким же образом определить любой кусок кода. Вот та же самая программа, но с использованием макроса для отделения фукнции разворота.
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 \
+
#define turn_around \
OnRev(OUT_B, 75); Wait(3400);OnFwd(OUT_AB, 75);
+
OnRev(OUT_B, 75); Wait(3400);OnFwd(OUT_AB, 75);
task main()
+
 
{
+
task main()
OnFwd(OUT_AB, 75);
+
{
Wait(1000);
+
  OnFwd(OUT_AB, 75);
turn_around;
+
  Wait(1000);
Wait(2000);
+
  turn_around;
turn_around;
+
  Wait(2000);
Wait(1000);
+
  turn_around;
turn_around;
+
  Wait(1000);
Off(OUT_AB);
+
  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 идёт имя turn_around которое будет означать текст указанный следом. Теперь, где бы вы не указали
Define statements are actually a lot more powerful. They can also have arguments. For example, we can put the
+
turn_around, это имя будет замещено определенным вами ранее текстом. Обратите внимание, что текст должен быть в одну строку. (Вообще то есть способ использования множественных строк внутри ключевого слова #define, но такой подход не рекомендован к использованию.)
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
+
На самом деле возможности #define еще более широкие. Они так же могут иметь аргументы. Например, мы можем сделать время поворота аргументом. Вот пример в котором мы определяем 4 макро: двигаться вперед, двигаться назад, поворачивать налево и поворачивать направо. Каждый из них имеет 2 аргумента - скорость и время выполнения.
the time.
+
 
#define turn_right(s,t) \
+
#define turn_right(s,t) \
OnFwd(OUT_A, s);OnRev(OUT_B, s);Wait(t);
+
OnFwd(OUT_A, s);OnRev(OUT_B, s);Wait(t);
#define turn_left(s,t) \
+
#define turn_left(s,t) \
OnRev(OUT_A, s);OnFwd(OUT_B, s);Wait(t);
+
OnRev(OUT_A, s);OnFwd(OUT_B, s);Wait(t);
#define forwards(s,t) OnFwd(OUT_AB, s);Wait(t);
+
#define forwards(s,t) OnFwd(OUT_AB, s);Wait(t);
#define backwards(s,t) OnRev(OUT_AB, s);Wait(t);
+
#define backwards(s,t) OnRev(OUT_AB, s);Wait(t);
task main()
+
{
+
task main()
backwards(50,10000);
+
{
forwards(50,10000);
+
  backwards(50,10000);
turn_left(75,750);
+
  forwards(50,10000);
forwards(75,1000);
+
  turn_left(75,750);
backwards(75,2000);
+
  forwards(75,1000);
forwards(75,1000);
+
  backwards(75,2000);
turn_right(75,750);
+
  forwards(75,1000);
forwards(30,2000);
+
  turn_right(75,750);
Off(OUT_AB);
+
  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.
 

Текущая версия на 15:11, 18 мая 2009

Автор: 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 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.

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

Существует еще один способ дать небольшому фрагменту кода собственное имя для обращения. В языке NXC вы можете определить макрос ( не путайте с макросами BricxCC). Мы видели ранее, что можем определять константы используя ключевое слово "#define", назначая им имена. Но на самом деле мы можем таким же образом определить любой кусок кода. Вот та же самая программа, но с использованием макроса для отделения фукнции разворота.

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

После ключевого слова #define идёт имя turn_around которое будет означать текст указанный следом. Теперь, где бы вы не указали turn_around, это имя будет замещено определенным вами ранее текстом. Обратите внимание, что текст должен быть в одну строку. (Вообще то есть способ использования множественных строк внутри ключевого слова #define, но такой подход не рекомендован к использованию.)

На самом деле возможности #define еще более широкие. Они так же могут иметь аргументы. Например, мы можем сделать время поворота аргументом. Вот пример в котором мы определяем 4 макро: двигаться вперед, двигаться назад, поворачивать налево и поворачивать направо. Каждый из них имеет 2 аргумента - скорость и время выполнения.

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

Очень полезно использовать такие макро, они делают вашу программу более компактной и читаемой. Кроме того вы можете более просто модифицировать код, например, переподключая двигатели к другим портам.

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

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

Встроенные функции полезны, когда куски когда используются в различных местах программы в различных задачах, но они занимают память. И наконец макро очень полезны для небольших кусочков кода, которые могут использоваться в различных местах. Кроме того они имеют аргументы, что делает их еще более мощным инструментом.

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