Пульт для беспилотника на базе Arduino и APC220. Часть 2
Дата публикации: Oct 05, 2013 7:50:14 PM
Итак, в одной из ранних статей я писал о создании пульта для квадрокоптера, на основе радиопередатчика APC220. По просьбам трудящихся, опишу подробнее по какому протоколу осуществляется общение пульта и машины.
APC220
Напомню, дабы не испытывать проблем с дальностью передачи, в самом начале проекта я закупил комплект из двух приемопередатчиков APC220. Эти волшебные устройства прохватывают сотни метров открытого пространства (по паспорту >2км). Подключаются они к микроконтроллеру через обычный UART. Соответственно, передача данных между двумя узлами осуществляется при помощи известного класса SoftwareSerial, который входит в состав штатных библиотек Arduino IDE.
Для примера, один узел заставим отправлять в эфир каждую секунду байт 0xA0.
APC Ведущий
SoftwareSerial apc(12,11);
#define BAUD_RATE 19200
#define SEND_DELAY 1000
unsigned int time;
unsigned int send_time;
void setup() {
apc.begin( BAUD_RATE );
}
void loop(){
time = millis();
if( time - send_time > SEND_DELAY ){
send_time = time;
apc.write( 0xA0 );
}
}
Второй узел будет слушать эфир, и при получении правильного байта - мигать светодиодом.
APC Ведомый
#define BAUD_RATE 19200
byte data;
bool led_state = 0;
void triggerLed(){
led_state != led_state;
digitalWrite( 13, led_state );
}
void setup() {
Serial.begin( BAUD_RATE );
}
void loop() {
if( Serial.available() ){
data = Serial.read();
if( data == 0xA0 )
triggerLed();
}
}
Библиотека SerialFlow
Как минимум, теперь мы умеем передавать байты. Осталось научиться передавать на машину управляющие команды и величины, а также получать от неё телеметрию. Конечно, можно обмениваться текстовыми сообщениями типа "engine on" или "thrust 110, dir 35", но это избыточно и некрасиво, верно? Можно было, наверное, воспользоваться готовыми библиотеками именно для таких целей. Но мне было проще написать свою библиотеку с блекджеком и, основанную на старом как свет протоколе.
В библиотеке SerialFlow описан класс, который оборачивает стандартный Serial, и позволяет передавать через него массивы двухбайтных чисел. Предположим, что команда 0xA1 отвечает за установку тяги двигателей, а 0x01FF - значение тяги (511 в десятеричной системе). Тогда пакет, передаваемый SerialFlow будет иметь вид:
0x12,0x0,0xA1,0x10,0xFF,0x01,0x13
Здесь 0x12 и 0x13 - стартовый и стоповый байты. 0x10 - разделитель. Есть еще один служебный байт - 0x7D, служащий для экранирования. В общем, имеем достаточно компактный пакет, который сильно экономит трафик радиоканала.
Для инициализации передатчика, нужно вызвать метод setPacketFormat:
apc.setPacketFormat( SerialFlow::COMPLEX, 2, 8 );
COMPLEX - тип пакета. В библиотеке предусмотрен еще SIMPLE, но в данном случае он нам не подходит.
2 - размер числа (2 байта, больше пока не предусмотрено);
8 - количество чисел в пакете.
Затем, когда нам нужно отправить числа, складываем их поочередно в пакет:
apc.setPacketValue( 0x00A1 );
apc.setPacketValue( 0x01FF );
Наконец, отправляем весь пакет:
apc.sendPacket();
Вот как будут выглядеть предыдущие программы, если в них использовать SerialFlow.
APC ведущий + SerialFlow
SerialFlow apc(12,11);
#define BAUD_RATE 19200
#define SEND_DELAY 1000
unsigned int time;
unsigned int send_time;
void setup() {
apc.setPacketFormat(SerialFlow::COMPLEX, 2, 1);
apc.begin( BAUD_RATE );
}
void loop(){
time = millis();
if( time - send_time > SEND_DELAY ){
send_time = time;
apc.setPacketValue( 0xA0 );
apc.sendPacket();
}
}
APC Ведомый + SerialFlow
#define BAUD_RATE 19200
SerialFlow apc( 12, 11 );
byte data;
bool led_state = 0;
void triggerLed(){
led_state != led_state;
digitalWrite( 13, led_state );
}
void setup() {
apc.setPacketFormat( SerialFlow::COMPLEX, 2, 1 );
apc.baud( BAUD_RATE );
}
void loop() {
if( apc.receivePacket() ){
data = apc.getPacket(0);
if( data == 0xA0 )
triggerLed();
}
}
Признаюсь, для мой задачи, в которой пульт служит лишь для первичной настройки машины, использование такого протокола особого смысла не имеет. Но я терпеть не могу передачу данных строками. К тому же, мой монитор порта SFMonitor заточен именно под SerialFlow.
Ведущий, ведомый, квитанции
Какими данными обмениваются пульт и мультикоптер? Во-первых - это самая важная команда включения/выключения. К этой команде вообще следует отнестись очень трепетно, особенно, если учесть повышенную травмоопасность винтокрылой машины. Вы всегда должны иметь возможность экстренно выключить мультикоптер. Никаких глюков здесь быть не может - рискуете кусочками своего тела. На втором месте идут команды управления: газ, рыскание, крен и тангаж. Далее, настроечные параметры, к которым относятся коэффициенты ПИД регулятора.
В своем проекте я наделил пульт правами ведущего (master). Мультикоптер же стал ведомым (slave). Каждые 50мс, мастер отправляет в эфир пакет управления. А каждые 500мс, беспилотнику дается команда о необходимости отправить телеметрию. После отправки пакетов включения/выключения, управления и телеметрии, ведущий дожидается квитанций о получении ведомым этих пакетов. Пока квитанция не получена, ведущий продолжает отправлять последнюю команду.
Полный код пульта и обоих контроллеров машины выложу чуть позже. Если есть конкретные вопросы, пишите письма.