Проект «Домовой». Глава 1.2

sheldon_whiteboard

Проблема с задержкой передачи данных в XBee

Я исключил из списка возможных проблем все: микроконтроллер, программу, библиотеки, все это не создавало проблем. Проверить было легко, я просто «общаться» со «стрелой» через USB через который все работало молниеносно. Так же я проверил общение через XBee на Arduino UNO что бы исключить возможные проблемы виртуальных портов «стрелы», но на Arduino были те же самые «затупы», даже с использование простевшей программы для включения и выключения светодиода из нескольких строк.

На скорость XBee грешить так же не приходилось, 9600 бод в секунду для отправки к примеру 5 команд размером 9 бит на команду (если мне не изменят память модули настроены как 8N1, то есть 8 бит данных + 1 стоповый бит, без контроля четности) это капля в море всего на всего 45 бит из 9600 возможных.
В общем было ясно что искать причину нужно во внутренней работе модулей XBee.

Мои модули XBee были настроены для организации сети типа «звезда», это когда имеется одно центральное устройство (координатор) и множество конечных устройств. Я сделал логическое предположение, а что если координатор сети выделяет некий промежуток времени после передачи определенного объема данных, для того что бы конечные устройства сети имели возможность ответить. Таким образом родилась мысль что если настроить два модуля только на общение друг с другом по принципу «точка-точка» то таких задержек быть не должно, тем более мне не нужна «сеть» мне нужна прямая связь двух модулей. С этой мыслью я пошел искать доки по XBee и читать что есть в интернете.

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

Я перенастроил свои модули на общение по принципу «точка-точка», для этого всего на всего достаточно в одном модуле прописать адрес назначения в виде адреса другого модуля и наоборот и они будут общается исключительно друг с другом.
Вот так выглядела настройка моих модулей:

xbee-point-to-point-configuration

Результатом стало то что все заработало и без единого «затупа», команды стали приходить на платформу молниеносно и в любом «разумном» количестве в секунду, все проблемы со связью исчезли.

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

Программа для платформы. v1.1

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

Дело в том что в v1.0 я предусмотрел ответ со стороны платформы только на команду «ping», но при разработке кода для ПК понял что желательно пусть и в самом примитивном варианте, но платформа должна подтверждать полученные команды, что бы ПК был уверен в том что команда не только доставлена но и распознана платформой и со спокойной совестью мог заниматься дальше своими делами.

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

Таким образом в программе для платформы v1.1 я решил добавить подтверждение каждой команды ответом «OK» который представлен константой OK_ANSWER, а если команда поступила, но платформа её не знает, то ответом «ERROR», который представлен константой «ERROR_ANSWER«.

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

Strela.cpp v1.1 : C/C++

#include <Wire.h>
#include <Strela.h>
#include <LiquidCrystal_I2C.h>

const char FORWARD_CMD = 'w';
const char BACK_CMD = 's';
const char LEFT_CMD = 'a';
const char RIGHT_CMD = 'd';
const char STOP_CMD = ' ';
const char PING_CMD = 'p';
const char OK_ANSWER = 'o';
const char ERROR_ANSWER = 'i';
const int RIGHT_ENGINE_SPEED = 165;
const int LEFT_ENGINE_SPEED = 185;
const int ZERO_SPEED = 0;
const int SAFETY_INTERVAL = 3000;
enum state {STOP, FORWARD, BACK, RIGHT, LEFT};

state currentState = STOP;
unsigned long lastCmdTime = 0;
LiquidCrystal_I2C lcd(LC_ADDR, LCEN, LCRW, LCRS, LC4, LC5, LC6, LC7);

void setup( void )
{
	motorConnection(1, 0);
	lcd.begin(8, 2);
	Serial1.begin(9600);
	printToLcd("Boggart:", "v1.1");
}


void loop( void )
{
	while (Serial1.available() < 1)
	{
		boolean isMoving = currentState != STOP;
		boolean isSafetyIntervalExceeded = (lastCmdTime + SAFETY_INTERVAL) < millis();
		if (isMoving && isSafetyIntervalExceeded)
		{
			stop();
		}
	}

	lastCmdTime = millis();
	switch (Serial1.read())
	{
		case FORWARD_CMD:
			confirmCommand();
			moveForward();
			break;
			
		case BACK_CMD:
			confirmCommand();
			moveBack();
			break;
			
		case LEFT_CMD:
			confirmCommand();
			turnLeft();
			break;
			
		case RIGHT_CMD:
			confirmCommand();
			turnRight();
			break;
			
		case STOP_CMD:
			confirmCommand();
			stop();
			break;
			
		case PING_CMD:
			confirmCommand();
			break;
		
		default:
			rejectCommand();
			printToLcd("Unknown", "command");
	}
}

inline void moveForward( void )
{
	if (currentState != FORWARD)
	{
		drive(RIGHT_ENGINE_SPEED, LEFT_ENGINE_SPEED);
		currentState = FORWARD;
		printToLcd("Move:", "Forward");
	}
}

inline void moveBack( void )
{
	if (currentState != BACK)
	{
		drive((RIGHT_ENGINE_SPEED * -1), (LEFT_ENGINE_SPEED * -1));
		currentState = BACK;
		printToLcd("Move:", "Back");
	}	
}

inline void turnLeft( void )
{
	if (currentState != LEFT)
	{
		drive(RIGHT_ENGINE_SPEED, ZERO_SPEED);
		currentState = LEFT;
		printToLcd("Turn:", "Left");
	}
}

inline void turnRight( void )
{
	if (currentState != RIGHT)
	{
		drive(ZERO_SPEED, LEFT_ENGINE_SPEED);
		currentState = RIGHT;
		printToLcd("Turn:", "Right");
	}
}

inline void stop( void )
{
	if (currentState != STOP)
	{
		drive(ZERO_SPEED, ZERO_SPEED);
		currentState = STOP;
		printToLcd("Boggart:", "Stop");
	}
}

inline void confirmCommand( void )
{
	Serial1.write( OK_ANSWER );
}

inline void rejectCommand( void )
{
	Serial1.write( ERROR_ANSWER );
}

void printToLcd( const char *inputLine1, const char *inputLine2 )
{
	lcd.clear();
	lcd.home();
	lcd.print( inputLine1 );
	lcd.setCursor(0, 1);
	lcd.print( inputLine2 );
}

* Код протестирован в Arduino IDE 1.0.5-r2

Скачать исходные коды Boggart v1.1

Хотя при таком подтверждении, платформа не знает а получил ли ПК подтверждение, но эта проблема не решается простым подтверждением подтверждения со стороны ПК, так как потом уже ПК не будет знать а получила ли платформа подтверждение ее подтверждения, в общем это бесконечно (Задача двух генералов).

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

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

Автор: Пашкевич Николай
pashkevich.me