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



Ниже приведены 2 программы - одна для мастера, а вторая для подчиненного. Эти простые программы покажут вам, как быстрый непрерывный поток строковых сообщений может быть передан с помощью радиоканала между двумя модулями NXT. Программа для мастера сначала убеждается, что подчиненный правильно подключен на 1 линию (константа BT_CONN) используя функцию BluetoothStatus(conn); после чего создает и отправляет сообщения состоящие из префикса "M" и возрастающих чисел с помощью функции SendRemoteString(conn,queue,string), одновременно получая сообщения от подчиненного функцией ReceiveRemoteString(queue,clear,string) и отображая эту информацию.

//MASTER#define BT_CONN 1#define INBOX 1#define OUTBOX 5 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); }} task main(){ string in, out, iStr; int i = 0; BTCheck(BT_CONN); //check slave connection while(true){ iStr = NumToStr(i);  out = StrCat("M",iStr); TextOut(10,LCD_LINE1,"Master Test"); TextOut(0,LCD_LINE2,"IN:"); TextOut(0,LCD_LINE4,"OUT:"); ReceiveRemoteString(INBOX, true, in); SendRemoteString(BT_CONN,OUTBOX,out); TextOut(10,LCD_LINE3,in); TextOut(10,LCD_LINE5,out); Wait(100); i++; }}

Программа подчиненного модуля очень похожа, но использует функцию SendResponseString(queue,string) вместо SendRemoteString, потому что подчиненный может отправлять сообщения только мастеру, находящемуся на линии 0.

//SLAVE#define BT_CONN 1#define INBOX 5#define OUTBOX 1 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); }} task main(){ string in, out, iStr; int i = 0; BTCheck(0); //check master connection while(true){ iStr = NumToStr(i); out = StrCat("S",iStr); TextOut(10,LCD_LINE1,"Slave Test"); TextOut(0,LCD_LINE2,"IN:"); TextOut(0,LCD_LINE4,"OUT:"); ReceiveRemoteString(INBOX, true, in); SendResponseString(OUTBOX,out); TextOut(10,LCD_LINE3,in); TextOut(10,LCD_LINE5,out); Wait(100); i++; }}

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

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

Как мы видим в приведенной ниже паре программ: в этот раз мастер отправлять числа командой SendRemoteNumber(conn,queue,number) и останавливается, ожидая подтверждения получения от подчиненного (цикл until, внутри которого мы видим ReceiveRemoteString); только если на той стороне подчиненный работает и отправил нам подтверждение - мастер переходит к отправке следующего сообщения. Подчиненный просто получает числа фукнцией ReceiveRemoteNumber(queue,clear,number) и отправляет подтверждение командой SendResponseNumber. Обе программы должны иметь общий код для подтверждения. В нашем случае я принял 0xFF в качестве такого кода.

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

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

//MASTER#define BT_CONN 1#define OUTBOX 5#define INBOX 1#define CLEARLINE(L) \TextOut(0,L," "); sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); }} task main(){ int ack; int i; BTCheck(BT_CONN); TextOut(10,LCD_LINE1,"Master sending"); while(true){ i = Random(512); CLEARLINE(LCD_LINE3); NumOut(5,LCD_LINE3,i); ack = 0; SendRemoteNumber(BT_CONN,OUTBOX,i); until(ack==0xFF) { until(ReceiveRemoteNumber(INBOX,true,ack) == NO_ERR); } Wait(250); }}//SLAVE#define BT_CONN 1#define OUT_MBOX 1#define IN_MBOX 5 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); }} task main(){ int in; BTCheck(0); TextOut(5,LCD_LINE1,"Slave receiving"); SendResponseNumber(OUT_MBOX,0xFF); //unblock master while(true){ if (ReceiveRemoteNumber(IN_MBOX,true,in) != STAT_MSG_EMPTY_MAILBOX) { TextOut(0,LCD_LINE3," "); NumOut(5,LCD_LINE3,in); SendResponseNumber(OUT_MBOX,0xFF); } Wait(10); //take breath (optional) }}

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

Вот еще одна отличная возможность доступная при использовании Bluetooth: мастер может напрямую контролировать подчиненных. В этом примере мастер отправляет подчиненным прямые команды для проигрывания звуков, включения двигателей и для подчиненного модуля NXT даже не нужно писать никакой программы. Это всё реализуется встроенным в NXT программным обеспечением!

//MASTER#define BT_CONN 1#define MOTOR(p,s) RemoteSetOutputState(BT_CONN, p, s, \OUT_MODE_MOTORON+OUT_MODE_BRAKE+OUT_MODE_REGULATED, \OUT_REGMODE_SPEED, 0, OUT_RUNSTATE_RUNNING, 0) sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); }} task main(){ BTCheck(BT_CONN); RemotePlayTone(BT_CONN, 4000, 100); until(BluetoothStatus(BT_CONN)==NO_ERR); Wait(110); RemotePlaySoundFile(BT_CONN, "! Click.rso", false); until(BluetoothStatus(BT_CONN)==NO_ERR); //Wait(500); RemoteResetMotorPosition(BT_CONN,OUT_A,true); until(BluetoothStatus(BT_CONN)==NO_ERR); MOTOR(OUT_A,100); Wait(1000); MOTOR(OUT_A,0);}

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

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

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

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

NXC имеет несколько сервисных команд. В этой главе мы обсудим три типа таких команд: для работы с таймером, дисплеем и для использования файловой системы NXT.

Таймеры

В модуле NXT есть таймер, который работает в постоянном режиме. Этот таймер увеличивается на единицу каждые 1/1000 секунды. Вы можете получить текущее значение таймера функцией CurrentTick(). Вот пример использования таймера, в котором робот будет двигаться случайным образом 10 секунд.

task main()

{

long t0, time;

t0 = CurrentTick();

do

{

time = CurrentTick()-t0;

OnFwd(OUT_AC, 75);

Wait(Random(1000));

OnRev(OUT_C, 75);

Wait(Random(1000));

}

while (time<10000);

Off(OUT_AC);

}

Можно попробовать сравнить эту программу с приведенной в главе 4, делающей то же самое. На таймере это делается определенно проще.

Таймеры очень полезны в качестве заменителя команд Wait(). Вы можете ожидать нужного момента времени сбросив таймер и ожидая нужного его значения. Но вы так же можете в это время реагировать на происходящие события (например на поступление информации от сенсоров), пока ждете. Следующая простая программа является примером такого использования таймеров. Она двигает робота вперед либо пока не пройдёт 10 секунд, либо пока у него не сработает бампер, упершись во что-нибудь.

task main()

{

long t3;

SetSensor(IN_1,SENSOR_TOUCH);

t3 = CurrentTick();

OnFwd(OUT_AC, 75);

until ((SENSOR_1 == 1) || ((CurrentTick()-t3) > 10000));

Off(OUT_AC);

}

Не забывайте, что таймер работает с шагом 1/1000 секунды, как и команда wait.

Дисплей

Модуль NXT имеет черно-белый графический дисплей с разрешением 100х64 пиксела. К нему есть множество функций API для рисования на нём текста, чисел, точек, линий, прямоугольников, кругов и даже картинок (из .ric-файлов). Следующий пример пытается покрыть все эти случаи. Пиксель (0,0) находится внизу слева экранчика.

#define X_MAX 99

#define Y_MAX 63

#define X_MID (X_MAX+1)/2

#define Y_MID (Y_MAX+1)/2

 

task main(){

int i = 1234;

TextOut(15,LCD_LINE1,"Display", true);

NumOut(60,LCD_LINE1, i);

PointOut(1,Y_MAX-1);

PointOut(X_MAX-1,Y_MAX-1);

PointOut(1,1);

PointOut(X_MAX-1,1);

Wait(200);

RectOut(5,5,90,50);

Wait(200);

LineOut(5,5,95,55);

Wait(200);

LineOut(5,55,95,5);

Wait(200);

CircleOut(X_MID,Y_MID-2,20);

Wait(800);

ClearScreen();

GraphicOut(30,10,"faceclosed.ric"); Wait(500);

ClearScreen();

GraphicOut(30,10,"faceopen.ric");

Wait(1000);

}

Все эти функции достаточно очевидны, но я всё-таки опишу их параметры детально:

  • ClearScreen() очищает экран;
  • NumOut(x, y, number) выводит число в указанные координаты;
  • TextOut(x, y, string) тоже самое, только выводит строку
  • GraphicOut(x, y, filename) выводит картинку из .ric-файла
  • CircleOut(x, y, radius) выводит окружность с заданными координатами центра и радиусом;
  • LineOut(x1, y1, x2, y2) рисует линию из (x1,x2) в (x2,y2)
  • PointOut(x, y) ставит точку
  • RectOut(x, y, width, height) рисует прямоугольник с нижним левым углом в (x,y) и указанных размеров;
  • ResetScreen() сбрасывает экран.

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

Модуль NXT может читать и писать файлы, имеющиеся у него в флеш-памяти. Так что вы можете создать лог-файл с датчиков или читать числа во время выполнения программы. Единственное ограничение в количестве и размере файлов - это размер флеш-памяти. Для работы с файлами используются функции API, которые позволяют создавать, переименовывать, удалять и искать файлы, а также с конкретным файлом вы можете читать и писать в него текстовые строки, числа и отдельные байты. В следующем примере мы посмотрим, как создать файл, записать в него несколько строк и переименовать этот файл.

Сначала, программа удаляет файлы с именами, которые мы хотим использовать: вообще это плохая практика (приличнее будет сначала проверить существование файла, вручную удалить его, или выбрать другое имя для нашего рабочего файла), но в нашей простой ситуации мы можем не обращать внимание на это. Дальше программа создает наш файл функцией CreateFile("Danny.txt", 512, fileHandle), указывая имя, размер и переменную, куда NXT поместит свой внутренний идентификатор открытого файла.

После чего программа готовит строки и пишет их в файл с возвратом каретки в конце функцией WriteLnString(fileHandle,string, bytesWritten), в которой все параметры должны быть переменными. И в конце, файл закрывается и переименовывается.

Помните: файл должен быть явно закрыт перед тем как начать следующую операцию, так что если вы создали файл, вы можете писать в него, если вы хотите прочитать из него - надо его закрыть и открыть на чтение командой OpenFileRead(). И перед удалением/переименованием файла он тоже должен быть закрыт.

#define OK LDR_SUCCESS

 

task main(){

byte fileHandle;

short fileSize;

short bytesWritten;

string read;

string write;

DeleteFile("Danny.txt");

DeleteFile("DannySays.txt");

CreateFile("Danny.txt", 512, fileHandle);

for(int i=2; i<=10; i++ ){

write = "NXT is cool ";

string tmp = NumToStr(i);

write = StrCat(write,tmp," times!");

WriteLnString(fileHandle,write, bytesWritten);

}

CloseFile(fileHandle);

RenameFile("Danny.txt","DannySays.txt");

}

Чтобы увидеть, что получилось, зайдите через меню "BricxCC => Tools => NXT Explorer", в просмотрщик NXT-флеш-памяти,загрузите файл DannySays.txt на ПК и посмотрите, что внутри.

Посмотрите на следующий пример! Мы создадим таблицу ASCII-символов.

task main(){

byte handle;

if (CreateFile("ASCII.txt", 2048, handle) == NO_ERR) {

for (int i=0; i < 256; i++) {

string s = NumToStr(i);

int slen = StrLen(s);

WriteBytes(handle, s, slen);

WriteLn(handle, i);

}

CloseFile(handle);

}

}

Правда просто? Эта программа создает файл и если при этом не произошла ошибка - пишет в него числа от 0 до 255 (конвертируя их предварительно в соответствующие символы функцией WriteBytes(handle, s, slen), которая предоставляет путь писать в файл строки без возврата каретки); после чего пишет это же число функцией WriteLn(handle, value), которая уже добавляет в конец записанной строки возврат каретки.

Результат, который вы видите при открытии файла ASCII.txt текстовым редактором (например Блокнотом из Windows), содержит в каждой строке символ и его код записанный в десятичном виде.

Остались еще две важные функции, которые вы должны знать: ReadLnString для чтения строк из файлов и ReadLn для чтения чисел.

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

После этого главная задача откроет этот файл на чтение, и до конца файла будет читать строку из файла, вызывая ReadLnString и показывать прочитанный текст.

В функции CreateRandomFile мы генерируем заданное количество случайных чисел, преобразуем их в строки и пишем их в файл.

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

#define FILE_LINES 10

 

sub CreateRandomFile(string fname, int lines){

byte handle;

string s;

int bytesWritten;

DeleteFile(fname);

int fsize = lines*5;

//create file with random data

if(CreateFile(fname, fsize, handle) == NO_ERR) {

int n;

repeat(FILE_LINES) {

int n = Random(0xFF);

s = NumToStr(n);

WriteLnString(handle,s,bytesWritten);

}

CloseFile(handle);

}

}

 

task main(){

byte handle;

int fsize;

string buf;

bool eof = false;

CreateRandomFile("rand.txt",FILE_LINES);

if(OpenFileRead("rand.txt", fsize, handle) == NO_ERR) {

TextOut(10,LCD_LINE2,"Filesize:");

NumOut(65,LCD_LINE2,fsize);

Wait(600);

until (eof == true){ // read the text file till the end

if(ReadLnString(handle,buf) != NO_ERR) eof = true;

ClearScreen();

TextOut(20,LCD_LINE3,buf);

Wait(500);

}

}

CloseFile(handle);

}

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

Вот директива препроцессора

#ifdef INT

…Code…

#endif

которая сообщает компилятору, что компилировать код внутри "#ifdef" нужно только если предварительно было определено значение INT. Так что если мы в начале программы объявим INT, скомпилируется задача "main" из первой половины кода, а если же мы определим LONG вместо INT, тогда будет скомпилирована вторая версия задачи "main".

Этот метод позволил мне в одной программе показать вам, как работать одной и той же функцией ReadLn(handle,val) с обоими типами чисел - int (16 бит) и long (32 бита).

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

Функция прочитает 2 байта из файла, если переданная переменная типа int, и 4 байта, если переменная типа long. Таким же образом вообще говоря можно читать и писать переменные типа bool.

#define INT // INT or LONG

#ifdef INT

 

task main () {

byte handle, time = 0;

int n, fsize,len, i;

int in;

DeleteFile("int.txt");

CreateFile("int.txt",4096,handle);

for (int i = 1000; i<=10000; i+=1000){

WriteLn(handle,i);

}

CloseFile(handle);

OpenFileRead("int.txt",fsize,handle);

until (ReadLn(handle,in)!=NO_ERR){

ClearScreen();

NumOut(30,LCD_LINE5,in);

Wait(500);

}

CloseFile(handle);

}

 

#endif

#ifdef LONG

 

task main () {

byte handle, time = 0;

int n, fsize,len, i;

long in;

DeleteFile("long.txt");

CreateFile("long.txt",4096,handle);

for (long i = 100000; i<=1000000; i+=50000){

WriteLn(handle,i);

}

CloseFile(handle);

OpenFileRead("long.txt",fsize,handle);

until (ReadLn(handle,in)!=NO_ERR){

ClearScreen();

NumOut(30,LCD_LINE5,in);

Wait(500);

}

CloseFile(handle);

}

 

#endif

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

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

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

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

Один из способов, победить такое поведение датчика - снять показания с каждой из трех цветовых компонент: красной, зеленой и синей, так чтобы по их значения самостоятельно (собственным алгоритмом) попытаться определить какого же цвета предмет. Такой способ уже был описан вот в этом материале. Но дело в том, что подразумевается, что у нас есть достаточно много времени для определения цвета, производя последовательно 3 или даже 4 (замер освещенности без подсветки вообще), и датчик и предмет неподвижны относительно друг друга. А это не всегда возможно достичь, особенно в условиях робототехнических состязаний - когда время работы робота является критичной величиной.

Для языка программирования Not eXactly C, тоже есть подобный инструментарий, позволяющий получить значения цветовых компонент за раз. Делается это с помощью функции ReadSensorColorRaw.

taskmain() {

SetSensorColorFull(S3);

 

unsigned intrawData[];

intresult = ReadSensorColorRaw(S3, rawData);

 

NumOut(8, LCD_LINE1, rawData[INPUT_RED]);

NumOut(8, LCD_LINE2, rawData[INPUT_GREEN]);

NumOut(8, LCD_LINE3, rawData[INPUT_BLUE]);

 

until(ButtonPressed(BTNCENTER));

}

 

Данный функционал тоже производит измерения достаточно быстро:

taskmain() {

SetSensorColorFull(S3);

 

unsigned intrawData[];

intresult;

   

unsigned longstrt = CurrentTick();

for(inti=0; i<5000; i++) {

   result = ReadSensorColorRaw(S3, rawData);

   NumOut(8, LCD_LINE1, rawData[INPUT_RED]);

   NumOut(8, LCD_LINE2, rawData[INPUT_GREEN]);

   NumOut(8, LCD_LINE3, rawData[INPUT_BLUE]);

}

NumOut(8, LCD_LINE5, CurrentTick() - strt);

 

until(ButtonPressed(BTNCENTER));

}

Результат работы - 5000 итераций за 6,2 секунды.
Если необходимо сделать за один вызов и определение цвета и получить значения компонент, то можно использовать функцию SysColorSensorRead.


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

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






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