События‎ > ‎

Пульт для беспилотника на базе Arduino и APC220. Часть 2

Отправлено 5 окт. 2013 г., 12:50 пользователем Олег Евсегнеев   [ обновлено 5 окт. 2013 г., 12:54 ]
Итак, в одной из ранних статей я писал о создании пульта для квадрокоптера, на основе радиопередатчика 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мс, беспилотнику дается команда о необходимости отправить телеметрию. После отправки пакетов включения/выключения, управления и телеметрии, ведущий дожидается квитанций о получении ведомым этих пакетов. Пока квитанция не получена, ведущий продолжает отправлять последнюю команду.

Полный код пульта и обоих контроллеров машины выложу чуть позже. Если есть конкретные вопросы, пишите письма.