Действия при срабатывании сенсора касания



Nbsp;

Оглавление

Первая программа_ 5

Постройка робота_ 5

Запуск Bricx Command Center 5

Написание программы_ 6

Запуск программы_ 7

Ошибки в программе_ 8

Изменяем скорость_ 9

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

Более интересная программа_ 10

Учимся делать повороты_ 10

Повторяем команды_ 10

Добавляем комментарии_ 11

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

Использование переменных 12

Движение по спирали_ 12

Случайные числа_ 14

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

Управляющие структуры_ 15

Оператор "if"_ 15

Оператор "do"_ 16

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

Сенсоры_ 17

Ждём информацию с сенсора_ 18

Действия при срабатывании сенсора касания_ 18

Сенсор освещенности_ 19

Звуковой сенсор_ 20

Ультразвуковой дальномер_ 21

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

Задачи и функции_ 22

Задачи_ 22

Функции_ 23

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

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

Создаём музыку_ 26

Проигрываем звуковые файлы_ 26

Играем музыку_ 27

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

Еще раз о моторах 28

Плавная остановка_ 29

Дополнительные команды_ 29

ПИД-управление_ 31

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

Дополнительная информация о сенсорах 32

Режимы и типы сенсоров_ 33

Сенсор вращения_ 34

Подключение нескольких сенсоров к одному входу_ 35

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

Параллельные задачи_ 37

Неправильная программа_ 37

Критические секции и "мьютекс"-переменные_ 38

Использование семафоров_ 39

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

Коммуникации между роботами_ 40

Отправка сообщений мастер-подчиненный_ 41

Отправка чисел с подтверждением_ 42

Прямые команды_ 44

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

Дополнительные возможности_ 44

Таймеры_ 45

Дисплей_ 45

Файловая система_ 46

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

Как определить цвет, когда датчик "врет"? 50

NXC: кнопочная интерактивность_ 51

NXC: работаем с датчиками вращения мотора_ 52

NXC: работаем с цветовым сенсором. 54

NXC: датчики, енкодеры, кнопки - взаимодействуем с внешним миром_ 55

Пример 1. Датчик расстояния_ 55

Пример 2. Измерение отраженного света. 55

Пример 3. Измерение окружающего света_ 56

Пример 4. Использование необработанных данных_ 56

Пример 5. Датчик вращения двигателя (енкодер) 57

Пример 6. Скорость опроса датчика расстояния_ 58

Пример 7. Определение цвета_ 58

Пример 8. Опрос датчика цвета в режиме датчика освещенности_ 59

NXC: использование циклов_ 59

Пример 2. while() в одну строчку_ 60

Пример 3. until() 60

Пример 4. Скорость исполнения цикла. 61

NXC: повороты - практика программирования моторов_ 61

Пример 1. Поворот одним двигателем_ 62

Пример 2. Поворот двумя двигателями_ 62

Пример 3. Распределение мощности при управлении двумя двигателями_ 63

Пример 4. Альтернативный поворот двумя двигателями_ 64

Сортировка данных на NXT_ 64

Пузырьковая сортировка_ 65

Сортировка вставками_ 66

Сортировка Шелла_ 67

Первая программа

В этой главе я покажу, как написать очень простую программу. Мы запрограммируем робота на движение 4 секунды вперед, 4 - назад и остановку в конце. Не очень впечатляюще, но на этом примере вы сможете получить представления об простейших способах программирования. Кроме того, вы сможете понять, насколько это просто. Но, прежде чем писать программу, нам нужно собрать робота.

Постройка робота

Робот, которого мы будем использовать в этом курсе - Tribot - первый колёсный робот, которого инструкция к NXT советует собрать после покупки набора. Единственное отличие будет заключаться в том, что вы должны подключить правый мотор в порт A, левый мотор в порт C, а мотор захвата в порт B.

Убедитесь, что вы правильно установили драйверы Mindstorms NXT Fantom Drivers, поставляемые с набором.

Запуск Bricx Command Center

Мы будем писать программы используя Bricx Command Center. Запустите его двойным щелчком по иконке BricxCC (я предполагаю, что вы уже установили BricxCC. Если нет, загрузите его с сайта (см. введение) и установите его в любой удобный вам каталог). Программа попросит предоставить ей возможность подключиться к роботу. Включите робота и нажмите "ОК". Программа (скорее всего) автоматически найдёт робота. Ниже приведено избражение интерфейса пользователя, появляющееся после успешного завершения синхронизации (закладки с текстовым окном быть не должно).

Интерфейс выглядит как стандартный текстовый редактор с обычным меню, кнопками для открытия и сохранения файлов, печати, редактирования и т.п. Однако среди них есть несколько специальных меню для компилирования программ и загрузки их в робота, а также для получения отладочной информации; пока вы можете не обращать на них внимание. Сейчас мы хотим написать новую программу, так что нажимайте кнопку "New File" для создания нового окна с текстовым редактором.

Написание программы

Теперь введите текст программы:

task main()

{

OnFwd(OUT_A, 75);

OnFwd(OUT_C, 75);

Wait(4000);

OnRev(OUT_AC, 75);

Wait(4000);

Off(OUT_AC);

}

На первый взгляд выглядит немного сложно, так что давайте разберемся в ней.

Программы NXC состоят из задач ("task"). Наша программа содержит всего одну задачу с именем "main". Любая программа на языке NXC должна содержать задачу с именем "main", которая и будет исполняться роботом (о задачах мы поговорим подробнее в главе 6). Задача состоит из набора команд - операторов. Операторы заключены в фигурные скобки для того, чтобы была ясна их принадлежность к конкретной задаче. Каждый оператор заканчивается точкой с запятой. Это позволяет понять, где кончается один оператор и начинается следующий. В целом задача обычно выглядит следующим образом:

task main()

{

statement1;

statement2;

}

Наша программа содержит 6 операторов. Давайте разберем их по очереди:

OnFwd(OUT_A, 75);

Этот оператор говорит роботу включить выход "A" модуля NXT. Это означает, что мотор, подключенный нами ранее, будет двигать модель вперед. Число, следующее за указанием порта, устанавливает скорость мотора в 75% от максимальной.

OnFwd(OUT_C, 75);

Тот же оператор, но теперь включаем мотор "C". После выполнения этих операторов оба мотора вращаются, и робот едет вперёд.

Wait(4000);

Теперь время немного подождать. Этот оператор говорит нам подождать 4 секунды. Аргумент, которым является число в скобках, указывается в 1/1000 секунды, так что мы можем с большой точностью указать программе необходимую задержку. Следующие 4 секунды программа будет ждать, а робот с включенными двигателями ехать вперед.

OnRev(OUT_AC, 75);

Робот проехал вперед достаточно далеко, так что мы скажем ему ехать в обратном направлении, т.е. назад. Обратите внимание, что мы можем установить режим работы обоих моторов сразу используя аргумент OUT_AC; таким же образом можно заменить первые два оператора задачи.

Wait(4000);

Снова ждём 4 секунды.

Off(OUT_AC);

И наконец, выключаем оба мотора.

Вот и вся программа. Она включает на 4 секунды оба мотора на движение вперед, затем назад и в конце выключает их.

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

Запуск программы

После написания программы нам потребуется её скомпилировать (превратить её в двоичные коды, которые робот может разобрать и выполнить) и переслать полученные двоичные коды в робота, используя USB-кабель или Bluetooth-донгл (сокращенно: загрузить программу в робота).

На этой панели расположены кнопки, которые позволяют (слева направо) компилировать программу, загружать её в робота, запускать выполнение программы на роботе и останавливать его.

Нажмите вторую кнопку, и, если вы не сделали ошибок при написании программы, она корректно скомпилируется и загрузится в робота (если вы допустили ошибки, об этом будет выдано сообщение, см. более подробно ниже).

Теперь мы можем запустить нашу программу на выполнение. Чтобы сделать это зайдите в меню "My Files" на модуле, "Software files", и запустите ("run") программу с названием "1_simple". Помните: файлы программ в файловой системе NXT имеют такие же имена, как и исходные NXC-файлы.

Кроме того, ваша программа может запускаться автоматически после загрузки. Используйте комбинацию клавиш CTRL+F5 или после загрузки программы на робота нажмите зелёную кнопку запуска программы.

Делает ли робот то, что мы запрограммировали? Если нет, проверьте соединения проводов.

Ошибки в программе

Когда вы набираете программы, существует вероятность допустить ошибки. Компилятор сообщает обо всех обнаруженных им ошибках внизу окна, в котором вы писали программу, как это показано на рисунке:

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

Могут быть ошибки, которые не обнаруживаются компилятором. Если мы указали OUT_B в управлении мотором, то будет вращаться не тот двигатель, который нам нужен. Если ваш робот ведёт себя не так, как вы запланировали, скорее всего вы что-то неправильно написали в управляющей им программе.

Изменяем скорость

Как вы заметили, робот перемещается достаточно быстро. Чтобы изменить его скорость, вам потребуется изменить второй параметр внутри круглых скобок. Скорость вращения моторов задаётся числом между 0 и 100: 100 - это самая большая скорость, 0 - полная остановка (сервопривод NXT будет удерживать положение). Вот новая версия нашей программы, под управлением которой робот будет перемещаться медленно:

task main()

{

OnFwd(OUT_AC, 30);

Wait(4000);

OnRev(OUT_AC, 30);

Wait(4000);

Off(OUT_AC);

}

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

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

Также вы освоили некоторые важные аспекты языка NXC. Прежде всего вы узнали, что каждая программа имеет задачу с именем "main", которая будет выполняться роботом. Также вы изучили 3 простые команды для управления двигателями: OnFwd(), OnRev() и Off(). И наконец, вы узнали об операторе Wait().

Более интересная программа

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

Учимся делать повороты

Вы можете заставить робота поворачиваться путём остановки или включении в обратном направлении одного из двух моторов. Вот пример того, как это можно сделать. Введите его, загрузите полученную программу в робота и запустите. Робот должен проехать немного вперед, после чего повернуться на 90 градусов вправо.

task main(){OnFwd(OUT_AC, 75);Wait(500);OnRev(OUT_C, 75);Wait(360);Off(OUT_AC);}

Вам может потребоваться попробовать немного другие числа, вместо 360 указанного во втором операторе Wait(), чтобы сделать 90 градусный поворот. Это зависит от типа поверхности по которой перемещается робот. Чтобы не отыскивать в больших программах такие числа, которые могут потребовать настройки существует возможность дать этому числу имя и определить его в начале программы. В языке NXC вы можете определять постоянные значения (константы) так, как показано в следующей программе:

#define MOVE_TIME 500#define TURN_TIME 360task main(){OnFwd(OUT_AC, 75);Wait(MOVE_TIME);OnRev(OUT_C, 75);Wait(TURN_TIME);Off(OUT_AC);}

Первые две строки определяют две константы. Теперь их можно использовать везде в программе вместо этих чисел. Использование констант хорошо по двум причинам: оно делает программы более читаемыми и более удобно менять значения констант, чем искать во всей программе где еще у вас время поворота было прописано как 360 тысячных секунды. Обратите внимание, что BricxCC обозначает операторы define отдельным цветом. Как мы увидим в главе 6, вы можете подобным образом определять не только константы.

Повторяем команды

Теперь давайте попробуем написать программу, которая заставить робота проехать по квадрату. Проехать по квадрату означает: проехать вперед, повернуться на 90 градусов, снова проехать вперед, снова повернуть на 90 градусов и так далее. Мы можем просто написать 4 раза кусок указанного выше кода, но значительно проще это можно сделать с использованием оператора цикла (repeat).

#define MOVE_TIME 500#define TURN_TIME 360task main(){ repeat(4) { OnFwd(OUT_AC, 75); Wait(MOVE_TIME); OnRev(OUT_C, 75); Wait(TURN_TIME); } Off(OUT_AC);}

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

В качестве окончательного примера давайте заставим робота 10 раз проехать по квадрату:

#define MOVE_TIME 500#define TURN_TIME 360task main(){ repeat(10) { repeat(4) { OnFwd(OUT_AC, 75); Wait(MOVE_TIME); OnRev(OUT_C, 75); Wait(TURN_TIME); } } Off(OUT_AC);}

Теперь у нас один оператор цикла внутри другого. Это называется “вложенный” оператор цикла. Вы можете делать операторы цикла любого уровня вложенности. Внимательно посмотрите на фигурные скобки и отступы, использованные в программе. Задача начинается с первой фигурной скобки и заканчивается последней. Первый оператор цикла начинается содержит операторы внутри второй и пятой фигурных скобок. Вложенный в него оператор цикла содержит операторы между третьей и четвертой фигурной скобкой. Как вы видите фигурные скобки всегда идут парами и кусок программы между ними выделяется отступом слева.

Добавляем комментарии

Чтобы сделать вашу программу еще более читаемой, неплохо бы добавить в неё комментарии. Когда мы пишем в строке // всё что после этих символов компилятор будет считать комментарием и проигнорирует. Длинный комментарий на несколько строк можно размещать между символами /* и */. BricxCC выделяет комментарии отдельным цветом в текстовом редакторе. Полный код программы с комментариями выглядит так:

/* 10 SQUARESThis program make the robot run 10 squares*/#define MOVE_TIME 500 // Time for a straight move#define TURN_TIME 360 // Time for turning 90 degreestask main(){ repeat(10) // Make 10 squares { repeat(4) { OnFwd(OUT_AC, 75); Wait(MOVE_TIME); OnRev(OUT_C, 75); Wait(TURN_TIME); } } Off(OUT_AC); // Now turn the motors off}

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

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

Использование переменных

Переменные составляют важную часть любого языка программирования. Переменные это ячейки памяти, в которых мы можем хранить какие-либо значения. Мы можем использовать эти значения в различных местах программы и мы можем изменять эти значения. Давайте разберем использование переменных на следующем примере.

Движение по спирали

Предположим, что мы хотим адаптировать приведенную выше программу таким образом, чтобы робот двигался по спирали. Это можно сделать путём увеличения времени которое робот движется прямо после каждого такого движения. То есть мы хотим после каждого такого движения увеличивать значение константы MOVE_TIME. Но как же мы сделаем это? Ведь MOVE_TIME это константа и поэтому не может быть изменена при выполнении программы. Поэтому нам потребуется заменить в нашей программе константу MOVE_TIME на переменную move_time. Как видно из программы, представленной ниже и реализующей движение по спирали, в языке NXC работа с переменными организована очень просто.

#define TURN_TIME 360int move_time; // define a variabletask main(){ move_time = 200; // set the initial value repeat(50) { OnFwd(OUT_AC, 75); Wait(move_time); // use the variable for sleeping OnRev(OUT_C, 75); Wait(TURN_TIME); move_time += 200; // increase the variable } Off(OUT_AC);}

Наиболее важные строки программы снабжены комментариями. Сначала мы объявляем переменную указывая ключевое слово "int" и следом имя переменной, которое мы выбрали. (Обычно принято использовать нижний регистр для имён переменных и верхний регистр для констант, но это не принципиально для компилятора.) Имя переменной должно начинаться с буквы, но может содержать в себе цифры и символ подчеркивания. Другие символы в именах переменных запрещены. (То же самое касается названий констант, задач и т.д.) Ключевое слово "int" означает "integer", то есть целое число. Только целые числа могут храниться в такой переменной.

Во второй строке мы присваиваем значение 200 нашей только что объявленной переменной. С этого момента при использовании переменной её значение будет 200. Теперь последуем внутрь цикла, в котором мы используем переменную для определения того, сколько времени двигаться вперед, и в конце цикла увеличим значение переменной на 200. Таким образом первый раз робот будет ехать прямо 200/1000=0.2 секунды, второй раз 0.4 секунды, третий раз 0.6 секунды и так далее.

Кроме добавления числа к переменной мы можем умножать её на число используя *=, вычитать из неё число используя -= и делить на число используя /=. (Обратите внимание, что для деления результат будет округлён до ближайшего целого.) Вы можете также добавлять одни переменные к другим и вообще записывать более сложные выражения. Следующий пример программы не будет никаким образом проявлять себя через исполнительные устройства робота, так как мы до сих пор не научились использовать дисплей NXT!

int aaa;int bbb,ccc;int values[];task main(){ aaa = 10; bbb = 20 * 5; ccc = bbb; ccc /= aaa; ccc -= 5; aaa = 10 * (ccc + 3); // aaa is now equal to 80 ArrayInit(values, 0, 10); // allocate 10 elements = 0 values[0] = aaa; values[1] = bbb; values[2] = aaa*bbb; values[3] = ccc;}

Заметим, что во второй строке мы объявили сразу несколько переменных за один раз. Мы можем таким образом объявить все переменные из первой и второй строки в одну строку. Переменная названная "values" является массивом, то есть, эта переменная может содержать больше чем просто число: элементы массива, к которым можно обращаться указывая число в квадратных скобках за переменной-массивом. В языке NXC массивы целых чисел объявляются так:

int name[];

Еще один интересный для нас оператор в приведённой программе - инициализация массива, который определяет, что в массиве values будет 10 элементов и инициализирует их все значением 0:

ArrayInit(values, 0, 10);

Случайные числа

Во всех приведенных ранее программах мы строго определяли что должен делать. Но может быть интересно создать ситуацию, в которой мы не будем знать что именно будет делать робот. Пусть мы хотим внести некую непредсказуемость в его движения. Для таких ситуаций в языке NXC существует возможность получения случайных чисел. Следующая программа показывает как сделать, чтобы робот перемещался случайным образом. Она без конца движется вперед случайное количество времени и потом поворачивается на случайный угол.

int move_time, turn_time;task main(){ while(true) { move_time = Random(600); turn_time = Random(400); OnFwd(OUT_AC, 75); Wait(move_time); OnRev(OUT_A, 75); Wait(turn_time); }}

Программа объявляет две переменные, а затем присваивает им случайные значения. Random(600) означает случайное число между 0 и 600 (при этом верхняя граница диапазона не включена во множество возможных ответов, т.е. вернётся число из диапазона 0..599). При каждом вызове функции Random получаемые числа будут отличаться. Обратите внимание, что мы могли избежать использования переменных и напрямую использовать полученное случайное число, написав оператор Wait(Random(600)).

Вы так же можете увидеть в этой программе новый тип цикла. Вместо использования цикла repeat мы использовали цикл while(true). Оператор цикла while повторяет включенные в него операторы до тех пор, пока условие заключенное в круглые скобки остаётся истинным. Специальное ключевое слово "true" всегда истинно, так что операторы этого цикла будут повторяться бесконечно (ну или по крайней мере до того момента, когда вы нажмёте тёмно-серую кнопку на вашем NXT). Больше информации об операторе while вы узнаете в главе 4.

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

В этой главе вы научились использованию переменных и массивов. Вы можете объявлять переменные других типов данных, отличных от "int": это могут быть "short", "long", "byte", "bool" и "string".

Типы данных

Int – целочисленный тип данных диапазон значений: 0 … 65 535

Short, int - целочисленный тип данных диапазон значений: –32 768 … 32 767

Char – целочисленный тип данных, диапазон значений: –128 … 127

Byte – целочисленный тип данных, диапазон значений: 0 … 255

Float – вещественный тип данных, диапазон значений: 3.4*10–38… 3.4*1038

Bool – логический тип данных, принимает значения true и false

 

Вы так же научились получать случайные числа, а значит вы можете добавить непредсказуемости в поведение робота. В конце главы мы увидели как может быть использован оператор цикла while для создания бесконечного цикла.

Управляющие структуры

В предыдущих главах мы видели операторы "repeat" и "while". Эти операторы управляют тем, каким образом выполняются или не выполняются другие операторы программы. Такие операторы называются “управляющие структуры”. В этой главе мы познакомимся с другими управляющими структурами.

Оператор "if"

Иногда возникает необходимость выполнять часть программы только в какой-то ситуации. В этом случае используется оператор "if". Давайте я вам покажу как это делается на примере. Для этого мы добавим новый трюк в программу, с которой мы работали в предыдущей главе. Мы хотим, чтобы робот проезжал прямо, а потом поворачивал случайным образом либо вправо, либо влево. Чтоб добиться такого поведения от робота мы опять будем использовать случайные числа. Мы берём случайное число, которое либо положительное, либо отрицательное. Если это число меньше 0 мы делаем поворот направо, иначе поворот налево. Вот получившаяся программа:

#define MOVE_TIME 500

#define TURN_TIME 360

task main()

{

while(true)

{

OnFwd(OUT_AC, 75);

Wait(MOVE_TIME);

if (Random() >= 0)

{

OnRev(OUT_C, 75);

}

else

{

OnRev(OUT_A, 75);

}

Wait(TURN_TIME);

}

}

Оператор "if" выглядит похожим на оператор "while". Если условие внутри круглых скобок истинно, тогда выполняется часть программы заключенная в фигурные скобки. В противном случае, выполняется часть программы заключенная в фигурные скобки после ключевого слова "else". Давайте подробнее взглянем на условие которое мы использовали. Оно выглядит как "Random() >= 0". Это означает что значение Random() должно быть больше или равно 0, чтобы условие считалось истинным. Вы можете сравнивать значения различными способами, вот наиболее важные из них:

  • "==" равно;
  • "<" меньше;
  • "<=" меньше или равно;
  • ">" больше;
  • ">=" больше или равно;
  • "!=" не равно.

Вы можете комбинировать условия используя ключевое связку "&&", которая означает логическое "и", или связку "||", которая означает логическое “или”. Вот некоторые примеры условий:

"true" всегда истинно

"false" никогда не истинно

"ttt != 3" истинно, когда "ttt" не равно 3

"(ttt >= 5) && (ttt <= 10)" истинно, когда "ttt" лежит между 5 и 10 включительно

"(aaa == 10) || (bbb == 10)" истинно, если "aaa" или "bbb" (или и то и другое) равно 10

Обратите внимание, что оператор "if" может имеет два блока кода внутри себя. Часть начинающаяся сразу после условия, которая будет выполнена если условие истинно, и часть после ключевого слова "else", которая будет выполнена в противном случае. Ключевое слово "else" и блок кода после него являются необязательными. Так что вы можете пропустить их, если не нужно ничего делать когда условие ложно.

Оператор "do"

Еще одна управляющая структура - это оператор "do". Его используют следующим образом:

do

{

statements;

}

while (condition);

Операторы внутри фигурных скобок после "do" выполняются до тех пор, пока условие в круглых скобках истинно. Условие может быть задано тем же способом, что и в операторе "if" рассмотренном выше. Вот пример программы с использованием этого оператора. Под её управлением робот перемещается случайным образом в течение 20 секунд, после чего останавливается.

int move_time, turn_time, total_time;

task main()

{

total_time = 0;

do

{

move_time = Random(1000);

turn_time = Random(1000);

OnFwd(OUT_AC, 75);

Wait(move_time);

OnRev(OUT_C, 75);

Wait(turn_time);

total_time += move_time;

total_time += turn_time;

}

while (total_time < 20000);

Off(OUT_AC);

}

Заметим, что оператор "do" практически один в один повторяет оператор "while". Но в операторе "while" условие проверяется перед выполнением блока операторов внутри него, а в операторе "do" это делается после. Таким образом в операторе "while" команды могут вообще не выполниться ни разу, а в операторе "do" они выполнятся 1 раз в любом случае.

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

В этой главе вы познакомились с двумя новыми управляющими структурами - оперторами "if" и "do". Вместе с рассмотренными ранее операторами "repeat" и "while" они управляют тем какие фрагменты программы будут выполняться и в каком порядке. Очень важно, чтобы вы полностью понимали как они это делают. Поэтому будет полезным перед переходом к следующей главе поэкспериментировать с новыми операторами, пока вы не будете уверены, что освоили их использование.

Сенсоры

 

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

 

Соедините полученный сенсор касания со входом 1 на модулей NXT.

Ждём информацию с сенсора

Давайте начнем с простой программы по которой робот будет ехать вперед, пока не коснётся чего-нибудь. Вот её текст:

task main(){ SetSensor(IN_1,SENSOR_TOUCH); OnFwd(OUT_AC, 75); until (SENSOR_1 == 1); Off(OUT_AC);}

В этой программе две строки для нас особо интересны. Первая строка программы указывает роботу какой тип сенсора мы используем. IN_1 это номер входа, к которому подключен сенсор. Другие входы для сенсоров имеют названия IN_2, IN_3 и IN_4. Идентификатор SENSOR_TOUCH показывает что это сенсор касания. Для датчика света мы будем использовать SENSOR_LIGHT. После того как мы указали тип сенсора и куда он подключен, программа включает оба мотора и робот начинает ехать вперед. Следующий оператор очень полезен. Он ожидает пока условие внутри его круглых скобок не станет истинным. Указанное там условие говорит что значение сенсора SENSOR_1 должно быть равно 1, что означает, что сенсор нажат. Пока сенсор не будет нажат, значение сенсора будет 0. Таким образом этот оператор ждёт нажатия сенсора. После чего оба мотора выключаются и задача считается завершенной.

Действия при срабатывании сенсора касания

Давайте теперь попытаемся сделать так, чтобы робот избегал препятствия. Когда робот будет сталкиваться с препятствием, мы сделаем чтобы он отъезжал немного назад, поворачивался и продолжал движение. Вот программа, которая это реализует:

task main(){ SetSensorTouch(IN_1); OnFwd(OUT_AC, 75); while (true) { if (SENSOR_1 == 1) { OnRev(OUT_AC, 75); Wait(300); OnFwd(OUT_A, 75); Wait(300); OnFwd(OUT_AC, 75); } }}

Как и в предыдущем примере, мы сначала определяем тип сенсора, затем робот начинает ехать вперед, а дальше в бесконечном цикле мы постоянно проверяем не оказался ли нажатым контактный сенсор, и если это так - движемся назад 0.3 секунды, поворачиваем направо в течение 0.3 секунд и затем продолжаем движение вперед.

 

Сенсор освещенности

Кроме датчика касания, у вас в Mindstorms NXT также имеется датчик освещенности, датчик звука и цифровой ультразвуковой дальномер. Датчик освещенности может быть настроен на излучение света или только на приём света. Таким образом мы можем измерять отраженный свет или просто уровень освещенности с какого-то направления. Измерение отраженного света полезно когда мы строим робота, который будет ездить по линии нанесённой на пол. Именно это мы и будем делать в следующем примере. А теперь, чтобы продолжить наши эксперименты, нужно достроить Tribot'a. Соедините датчик освещенности с входом 3, датчик звука ко входу 2 и ультразвуковой дальномер ко входу 4, как указано в инструкции.

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

#define THRESHOLD 40task main(){ SetSensorLight(IN_3); OnFwd(OUT_AC, 75); while (true) { if (Sensor(IN_3) > THRESHOLD) { OnRev(OUT_C, 75); Wait(100); until(Sensor(IN_3) <= THRESHOLD); OnFwd(OUT_AC, 75); } }}

Программа сначала объявляет порт 3 датчиком освещенности. Затем робот включается на движение вперед и запускается бесконечный цикл. В нём, если уровень освещенности больше чем 40 (мы использовали константу, чтобы можно было удобно настраивать робота, так как наш метод отслеживания линии сильно зависит от уровня освещенности помещения, где проводится эксперимент) мы включаем один из моторов в режим обратного хода пока снова не вернемся на линию.

Как можно увидеть из выполнения программы - движение робота не очень плавные. Попробуйте добавить Wait(100); перед оператором until, чтобы немного улучшить движение робота. Обратите внимание, что программа не работает для движения против часовой стрелки. Чтобы можно было двигаться по любому направлению линии потребуется другая, более сложная, программа.

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

SetSensorType(IN_3,IN_TYPE_LIGHT_INACTIVE);SetSensorMode(IN_3,IN_MODE_PCTFULLSCALE);ResetSensor(IN_3);

Звуковой сенсор

Используя звуковой сенсор вы можете превратить свой дорогостоящий набор NXT в "выключатель по хлопку"! Мы напишем программу, которая ждёт громкого звука, после чего включает робота на движение вперед, пока не услышит еще один громкий звук. Не забудьте проверить подключение звукового сенсора к порту 2, как указано в инструкции по сборке Tribot'a.

#define THRESHOLD 40#define MIC SENSOR_2task main(){ SetSensorSound(IN_2); while(true){ until(MIC > THRESHOLD); OnFwd(OUT_AC, 75); Wait(300);   until(MIC > THRESHOLD); Off(OUT_AC); Wait(300); }}

Сначала мы определяем константу уровня звука с которого мы будем считать его громким - THRESHOLD и псевдоним для SENSOR_2; в самой задаче мы настраиваем порт 2 на чтение данных со звукового сенсора и запускаем бесконечный цикл.

Используя оператор until программа ждёт уровня звука который будет больше чем объявленный в THRESHOLD: обратите внимание, что SENSOR_2 это не просто имя, а функция, которая возвращает уровень звука замеренный сенсором.

Если будет обнаружен громкий звук робот начнёт движение вперед, пока еще один громкий звук не остановит его.

Операторы wait были добавлены в программу потому что иначе робот будет запускаться и тут же останавливаться: на самом деле NXT настолько быстр, что у него практически не занимает времени выполнение программы между двумя операторами until. Пожалуй, если вы попробуете закомментировать оба оператора wait, вы лучше поймёте о чем идёт речь. Альтернативой использованию оператора until для ожидания событий может быть оператор while. Достаточно поместить в его круглые скобки соответствующее условие, например

while(MIC <= THRESHOLD).

На самом деле не так много нужно помнить про аналоговые сенсоры из набора NXT; просто не забывайте, что оба датчика - освещенности и звуковой возвращают вам число от 0 до 100.

Ультразвуковой дальномер

Ультразвуковой дальномер работает как сонар. Излучая громкий звук он посылает пачку ультразвуковых волн и замеряет время, которое потребовалось им, чтобы отразиться от объектов в поле зрения и вернуться к датчику. Это цифровой сенсор, что означает, что в него встроено устройство для анализа и обработки данных. С этим сенсором вы можете построить робота, который будет избегать препятствий, а не обнаруживать их путём столкновения (как это приходилось делать с датчиком касания).

#define NEAR 15 //cmtask main(){ SetSensorLowspeed(IN_4); while(true){ OnFwd(OUT_AC,50); while(SensorUS(IN_4)>NEAR); Off(OUT_AC); OnRev(OUT_C,100); Wait(800); }}

Программа объявляет порт 4 для чтения данных с ультразвукового дальномера, запускает бесконечный цикл в котором робот едет прямо, пока не обнаружит препятствие ближе чем NEAR сантиметров (15см в нашем случае), после этого он отъезжает и отворачивает немного и снова пытается ехать прямо.

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

В этой главе вы ознакомились с тем, как работать со всеми видами датчиков включенных в набор NXT. Мы также увидели, как полезны операторы while и until при работе с сенсорами.

Я рекомендую вам попробовать самостоятельно написать несколько программ до перехода к следующей главе. Теперь у вас есть все компоненты, чтобы ваш робот показывал достаточно сложное поведение: попробуйте адаптировать под NXC простейшие программы из "Robo Center programming guide", поставляемого с набором.

Задачи и функции

До этого момента все наши программы состояли из просто одной задачи. Но программы на языке 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);}

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

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

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

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

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

Создаём музыку

Модуль NXT имеет встроенный динамик и может воспроизводить музыку и даже звуковые файлы. Это может быть полезно в том случае, когда вы хотите чтобы NXT сказал вам, что что-то случилось. Но это также может быть просто более интересно, когда робот играет музыку или разговаривает в процессе выполнения заданий.

Проигрываем звуковые файлы

BricxCC содержит в себе встроенную утилиту для преобразования .wav-файлов в .rso-файлы, доступную через меню "Tools" / "Sound conversion".

Завершив преобразование, вы можете сохранить .rso-файлы со звуками во флеш-память NXT при помощи другой утилиты (меню "Tools" / "NXT explorer"), после чего их можно будет воспроизводить командой

PlayFileEx(filename, volume, loop?)

Аргументы команды - имя файла, громкость (число от 0 до 4), и необязательный флаг зацикливания "loop", выставьте его в 1 (или TRUE), если хотите, чтобы файл вопроизвёлся не один раз, выставление его в 0 (или FALSE) воспроизведёт файл один раз.

#define TIME 200

#define MAXVOL 7

#define MINVOL 1

#define MIDVOL 3

#define pause_4th Wait(TIME)

#define pause_8th Wait(TIME/2)

#define note_4th \

PlayFileEx("! Click.rso",MIDVOL,FALSE); pause_4th

#define note_8th \

PlayFileEx("! Click.rso",MAXVOL,FALSE); pause_8th

task main()

{

PlayFileEx("! Startup.rso",MINVOL,FALSE);

Wait(2000);

note_4th;

note_8th;

note_8th;

note_4th;

note_4th;

pause_4th;

note_4th;

note_4th;

Wait(100);

}

Эта забавная программа сначала проигрывает звук начала работы, который вы возможно уже слышали, а затем использует стандартный звук "click" чтобы сыграть джингл “Shave and a haircut” который приводит в восторг кролика Роджера! Макрос действительно очень удобен в данном случае для упрощения записи команд в главное задаче: попробуйте модифицировать настройки громкости чтобы сделать мелодию наиболее приятной.

Играем музыку

Чтобы воспроизвести тоновый сигнал вы можете использовать команду PlayToneEx(frequency, duration, volume, loop?) Она имеет 4 параметра. Первый это частота тона в Герцах, второй - продолжительность в 1/1000 секунды (как в операторе "wait"), и последний - такой же флаг зацикливания, как и в предыдущей команде. Кроме того может быть использована команда PlayTone(frequency, duration), в этом случае громкость используется настроенная в меню вашего модуля NXT, а функция зацикливания недоступна.

Вот таблица с полезными частотами музыкальных нот по октавам:

Нота 3 4 5 6 7 8 9
B (Си) 247 494 988 1976 3951 7902  
A# (Ля-диез) 233 466 932 1865 3729 7458  
A (Ля) 220 440 880 1760 3520 7040 14080
G# (Соль-диез)   415 831 1661 3322 6644 13288
G (Соль)   392 784 1568 3136 6272 12544
F# (Фа-диез)   370 740 1480 2960 5920 11840
F (Фа)   349 698 1397 2794 5588 11176
E (Ми)   330 659 1319 2637 5274 10548
D# (Ре-диез)   311 622 1245 2489 4978 9956
D (Ре)   294 587 1175 2349 4699 9398
C# (До-диез)   277 554 1109 2217 4435 8870
C (До)   262 523 1047 2093 4186 8372

Как и в случае с PlayFileEx, модуль NXT не дожидается когда нота доиграет. Так что если вы хотите воспроизвести подряд несколько нот - вам придётся добавить между ними достаточно долгие команды "wait". Например вот так:

#define VOL 3

task main()

{

PlayToneEx(262,400,VOL,FALSE); Wait(500);

PlayToneEx(294,400,VOL,FALSE); Wait(500);

PlayToneEx(330,400,VOL,FALSE); Wait(500);

PlayToneEx(294,400,VOL,FALSE); Wait(500);

PlayToneEx(262,1600,VOL,FALSE); Wait(2000);

}

Вы можете с легкость создавать музыкальные фрагменты с использованием инструмента "Brick Piano", являющегося частью BricxCC. Если вы хотите, чтобы NXT играл музыку, когда выполнял различные задачи - лучше используйте для этого отдельную задачу. Вот пример достаточно достаточно глупой программы, где NXT ездит взад-вперед и параллельно играет музыку.

task music()

{

while (true)

{

PlayTone(262,400); Wait(500);

PlayTone(294,400); Wait(500);

PlayTone(330,400); Wait(500);

PlayTone(294,400); Wait(500);

}

}

 

task movement()

{

while(true)

{

OnFwd(OUT_AC, 75); Wait(3000);

OnRev(OUT_AC, 75); Wait(3000);

}

}

 

task main()

{

Precedes(music, movement);

}

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

В этой главе вы научились как с помощью NXT проигрывать звуки и музыку. Кроме того вы увидели, как использовать отдельную задачу для воспроизведения музыки.

Еще раз о моторах

Существует несколько дополнительных команд для моторов, которые вы можете использовать для более точного управления моторами. В этой главе мы обсудим эти команды: ResetTachoCount, Coast (Float), OnFwdReg, OnRevReg, OnFwdSync, OnRevSync, RotateMotor, RotateMotorEx, а также основы ПИД-управления.

Плавная остановка

Когда вы используете команду Off(), сервомотор мгновенно останавливается, блокируя вал и удерживая позицию. Существует способ более мягкой остановки мотора не используя блокировку вала. Для этого используйте команды Float() или что то же самое - Coast(), они просто отключают питание моторов. Вот пример, сначала робот останавливается используя блокировку вала, а потом останавливается без использования блокировки. Обратите внимание на разницу. На самом деле для конкретно этого робота разница достаточно незаметна, но для других роботов эта разница будет гораздо больше.

task main(){ OnFwd(OUT_AC, 75); Wait(500); Off(OUT_AC); Wait(1000); OnFwd(OUT_AC, 75); Wait(500); Float(OUT_AC);}

Дополнительные команды

Команды OnFwd() и OnRev() являются простейшими командами для управления моторами.

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

Встроенная в NXT прошивка реализует алгоритм ПИД-управления (Пропорциональный Интегральный Дифференциальный - от названия основных трёх его компонент) положением вала и скоростью его вращения с обратной связью через энкодеры.

Если вы хотите, чтобы ваш робот ездил строго по прямой, вы можете использовать возможность синхронизации двигателей, которая объединяет их в пару, в которой каждый двигатель замедляется, если второй не успевает крутиться с нужной скоростью. Подобным образом вы можете настроить пару моторов на работу в синхронизированном режиме с процентом скорости левого двигателя по отношению к правом, для поворота направо, налево или вращения на месте, при этом траектория будет выполняться достаточно точно. Существует множество команд, которые позволяют полностью использовать возможности сервомоторов из набора NXT!

Команда OnFwdReg(‘ports',‘speed',‘regmode') включает двигатели указанные в ‘ports' на движение вперед со скоростью ‘speed' применяя режим регуляции, который может быть OUT_REGMODE_IDLE, OUT_REGMODE_SPEED или OUT_REGMODE_SYNC. Если выбран режим IDLE, ПИД-регулирование будет отключено; если режим SPEED, Модуль NXT будет регулировать скорость отдельных моторов чтобы получить постоянную скорость, даже если нагрузка на мотор меняется; и наконец, если выбран режим SYNC, пара моторов указанная в ‘ports' работают в синхронизированном режиме, как было описано выше.

Команда OnRevReg() работает так же как и выше, только в другую сторону.

task main(){ OnFwdReg(OUT_AC,50,OUT_REGMODE_IDLE); Wait(2000); Off(OUT_AC); PlayTone(4000,50); Wait(1000); ResetTachoCount(OUT_AC); OnFwdReg(OUT_AC,50,OUT_REGMODE_SPEED); Wait(2000); Off(OUT_AC); PlayTone(4000,50); Wait(1000); OnFwdReg(OUT_AC,50,OUT_REGMODE_SYNC); Wait(2000); Off(OUT_AC);}

Эта программа показывает различные способы регулирования. Если вы попробуете остановить колёса робота, удерживая их в руках, при первом режиме (IDLE) ничего не будет происходить, во втором режиме (SPEED) попытка замедлить колесо вызовет увеличение мощности подаваемой на этот мотор, так как NXT пытается добиться заданной ему скорости; и наконец, в режиме синхронизации (SYNC), если попытаться остановить одно колесо - второе так же остановится, дожидаясь заблокированного колеса.

OnFwdSync(‘ports',‘speed',‘turnpct') делает то же самое что OnFwdReg() в режиме SYNC, но можно указать 'turnpct' - процент поворота влево\вправо (от -100 до 100).

OnRevSync() делает то же самое что и предыдущая команда, только направления обратные. Следующая программа показывает эти команды в работе: попробуйте менять процент поворота, чтобы увидеть как это влияет на работу двигателей.

task main(){ PlayTone(5000,30); OnFwdSync(OUT_AC,50,0); Wait(1000); PlayTone(5000,30); OnFwdSync(OUT_AC,50,20); Wait(1000); PlayTone(5000,30); OnFwdSync(OUT_AC,50,-40); Wait(1000); PlayTone(5000,30); OnRevSync(OUT_AC,50,90); Wait(1000); Off(OUT_AC);}

И наконец, моторы можно поворачивать на конкретное число градусов (помните, что полный оборот это 360°).

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

RotateMotor(‘ports',‘speed',‘degrees') вращает вал мотора указанного в ‘ports' на ‘degrees' градусов со скоростью ‘speed' (в диапазоне 0-100).

task main(){ RotateMotor(OUT_AC, 50,360); RotateMotor(OUT_C, 50,-360);}

RotateMotorEx(‘ports',‘speed',‘degrees',‘turnpct',‘sync', 'stop') это расширение предыдущей команды, которое позволяет синхронизировать два мотора (например, OUT_AC) указывая процент поворота ‘turnpct' (от -100 до 100) и флаг ‘sync' (который может быть или истиной или ложью). Также команда позволяет указать - нужно ли по её завершении блокировать вал двигателя, для этого используется флаг 'stop'.

task main(){ RotateMotorEx(OUT_AC, 50, 360, 0, true, true); RotateMotorEx(OUT_AC, 50, 360, 40, true, true); RotateMotorEx(OUT_AC, 50, 360, -40, true, true); RotateMotorEx(OUT_AC, 50, 360, 100, true, true);}

ПИД-управление

Встроенное программное обеспечение NXT реализует цифровой ПИД-регулятор для точного управления положением и скорости сервомоторов. Этот тип управления один из самых простых и при этом эффективных способов управления с обратной связью, широко используемый во всяких задача автоматизации. Если кратко, он работает так (Я буду говорить об управлении положением вала цифровым контроллером):

Ваша программа задаёт контроллеру требуемое положение R(t); он выдаёт мощность U(t) на двигатель, замеряя положение вала Y(t) встроенными энкодерами и рассчитывает отклонение E(t) = R(t) – Y(t): вот почему это называется контроллер с обратной связью, потому что выходное положение вала мотора возвращается на вход контроллера, чтобы посчитать насколько он сейчас промахивается. Контроллер при этом превращает размер отклонения E(t) в новую команду для двигателя U(t) таким образом:

U(t) = P(t) + I(t) + D(t), где

P(t) = KP·E(t),

I(t) = KI·( I(t–1) + E(t) )

и D(t) = KD·(E(t) – E(t –1)).

Это может выглядеть достаточно сложно для новичка, но я предприму все усилия, чтобы помочь вам объяснить этот механизм. Команда является суммой трёх частей, Пропорциональной части P(t), интегральной I(t) и дифференциальной D(t).

P(t) даёт контроллеру быстроту реакции, но может мешать стабильности в долгосрочной перспективе;

I(t) даёт контроллеру “память”, в том смысле, что накапливает ошибку и компенсирует её, гарантируя долгосрочную стабилизацию в нужном положении;

D(t) даёт контроллеру “возможность предугадывать” (на основе производной из математики), ускоряя время реакции системы.

Я понимаю, что это может всё еще казаться запутанным, на самом деле на эту тему написаны целые научные книги! Но мы всё еще можем попробовать это в реальности с нашим NXT! Простая программа, которая поможет зафиксировать в памяти эту тему:

#define P 50#define I 50#define D 50task main(){ RotateMotorPID(OUT_A, 100, 180, P, I, D); Wait(3000);}

Функция RotateMotorPID(port,speed, angle, Pgain,Igain,Dgain) позволяет управлять мотором устанавливая различные параметры ПИД-регулятора, отличные от настроек по умолчанию. Попробуйте выставлять следующие значения:

(50,0,0): мотор не поворачивается точно на 180°, так как остаётся нескомпенсированная ошибка

(0,x,x): без пропорциональной части ошибка очень большая

(40,40,0): здесь у нас перелёт, т.е. мотор поворачивается дальше чем надо и потом возвращается

(40,40,90): хорошая точность и время выполнения команд

(40,40,200): вал колеблется, так как дифференциальная часть слишком большая

Попробуйте другие значения, чтобы посмотреть как разные настройки ПИД-регулятора влияют на выполнение команд.

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

В этой главе вы освоили дополнительные команды для управления моторами: Float(),Coast() которые плавно останавливают моторы; OnXxxReg() и OnXxxSync() которые реализуют управление с обратной связью как по скорости, так и по синхронизации со вторым мотором; RotateMotor() и RotateMotorEx() используемые для поворота на нужный угол выходного вала моторов. Вы немного изучили что такое ПИД-регулирование; хотя это было не исчерпывающее объяснение, может быть я вас даже немного запутал, попробуйте поискать материал на эту тему в Интернете!


Дата добавления: 2018-05-12; просмотров: 559; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!