Делаем простую RC-аэролодку

Иногда в интернете вижу вопросы от новичков, которые хотят с нуля построить квадрокоптер и написать к нему прошивку. Сам являюсь таким и чтобы попрактиковаться в создании RC моделей решил начать с чего-то более простого.
7dd6f246e98e47b8b5f3eda1ccdd14b0.JPG
В статье в подробностях для самых маленьких описал алгоритм работы лодки, пульта управления и выбор компонентов.

Почему аэролодка?


  1. Просто;
  2. Дёшево;
  3. Она может двигаться в природных условиях;
  4. Можно тренироваться настраивать разные варианты управления, включая ПИД-регулятор.


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

Платформы по типу этой
2ea8573051be4afcbe6aaf40c94a753d.jpg
(тут есть более подробное описание).
умеют двигаться только по, в большинстве случаев, искусственной ровной поверхности, да и управление у них сильно отличается.

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

  1. Его можно ставить на разные платформы: лодку, сани, кусок пенопласта…
  2. Он не зацепится за дно или водоросли.


Надо чтобы аппарат мог поворачивать. Есть 3 варианта:

  1. Один винт + руль для поворотов;
  2. Один винт + система его поворачивающая;
  3. Два жёстко закреплённых винта. Поворачивать меняя их тягу — самый простой способ. Его и использовал.

Пульт дистанционного управления


Принцип действия


1 джойстик + несколько переключателей. Задача пульта — несколько раз в секунду посылать в эфир данные о положении ручки джойстика и переключателей.

Из чего делать


Во-первых, нужен радиопередатчик. Самый дешёвый вариант — это NRF24L01+, стоит $0.85
Во-вторых, нужен джойстик. Ещё $1.
Несколько переключателей — $0.12.
Ну и всё это закрепить на куске текстолита за $0.13.
Уже насчитали $2.1, а нужен ещё МК и питание. Тут всё не так однозначно.
Забегая вперёд, скажу, что вполне достаточно ATmega8 или STM8S103F3P6, но так как начинал этот проект я давно и опыта было мало, влепил в пульт управления Arduino Pro Mini, а в лодку Arduino Nano (везде ATmega32P).

В пульт ещё понадобились:

  1. Преобразователь питания 0.9 — 5 В → 5 В для питания Arduino за $0.35 (разъём USB, вместе с куском платы, можно отломать для компактности);
  2. Стабилизатор 3.3 В AMS1117–3.3 для питания радиомодуля, стоят они по $0.03 в пересчёте на штуку;
  3. Батарейный отсек для одного пальчикового аккумулятора за $0.15;


Всего плюс $0.53. Не считая контроллера, пары конденсаторов и проводов, стоимость компонентов пульта получается $2,63.

Начинка радиоуправляемой модели


Компоненты


Тут всё идёт от двигателей. Какие двигатели купите, такой мощности электронику и придётся ставить, и база (судно, сани) потребуются соответствующей грузоподъёмности. Да и идеологически, всё остальное нужно лишь для того, чтобы вращать винты с нужной скоростью.
Я купил вот такие моторчики с пропеллерами
02a8110476e840e68848408e6fcc4872.jpg
по $2.88 за пару.
В качестве драйвера моторов взял L293D — ещё $0.35.

И тут огрёб проблем

Для L293D коммутируемое напряжение должно быть не ниже напряжения питания логики. А эти моторчики весьма сильно просаживали напряжение в результате чего ток от питания логики перетекал в моторы и питание логики вместе с питанием микроконтроллера тоже просаживалось вплоть до того, что МК отключался.


Питание. Нам потребуются аж три напряжения питания:

  1. 5 В для всей электроники кроме радиомодуля;
  2. 3.3 В для радиомодуля;
  3. для моторчиков сколько им нужно (моим 4.2 В).


1 и 2 получим также как в пульте управления, а для моторчиков поставим MT3608 за $0.86.

Теперь самое интересное: гироскоп. Модуль MPU-6050 стоит $1.53. Было желание использовать ещё и акселерометр, чтобы при движении ручки джойстика в сторону судно разворачивалось на месте. Но в итоге от этой идеи отказался: небольшой наклон по тангажу, и система начинает думать, что она ускоряется вперёд или назад. Проще оказалось разворачивать судно на месте просто компенсируя джойстиком движение вперёд/назад.

Добавим к этому $0.4 за батарейный отсек на 2 элемента AA и получим компонентов на $6.4 без контроллера и проводов.

Программа


И снова пойдём от двигателей. Каждый из двух двигателей управляемый L293D может копать, а может не копать:

  1. Крутить вперёд;
  2. Крутить назад;
  3. Не крутить.


Чтобы код легче читался, напишем

6 функций
inline void motLeftStop(){
  PORTD &= ~(1 << MOT_LEFT_PLUS);
  PORTD &= ~(1 << MOT_LEFT_MINUS);
}

inline void motLeftForward(){    
  PORTD |= 1 << MOT_LEFT_PLUS;
  PORTD &= ~(1 << MOT_LEFT_MINUS);
}

inline void motLeftBackward(){
  PORTD &= ~(1 << MOT_LEFT_PLUS);
  PORTD |= 1 << MOT_LEFT_MINUS;
}

inline void motRightStop(){
  PORTD &= ~(1 << MOT_RIGHT_PLUS);
  PORTD &= ~(1 << MOT_RIGHT_MINUS);
}

inline void motRightForward(){
  PORTD |= 1 << MOT_RIGHT_PLUS;
  PORTD &= ~(1 << MOT_RIGHT_MINUS);
}

inline void motRightBackward(){
  PORTD &= ~(1 << MOT_RIGHT_PLUS);
  PORTD |= 1 << MOT_RIGHT_MINUS;
}



Теперь мы хотим управлять скоростью вращения винтов. Конечно, делать это будем при помощи ШИМ. Не знаю, можно ли такую ШИМ сделать аппаратно… я сделал программно на прерываниях. Объявим пару глобальных переменных

int8_t motLeft = 0, motRight = 0; // -127..+127


Пусть значения этих переменных 0 — вперёд, а если они равны 0, то крутить не надо.

Напишем обработчики прерываний таймера
ISR(TIMER2_OVF_vect)
{
  if(motLeft > 0)
    motLeftForward();
  else if(motLeft < 0)
    motLeftBackward();
  if(motRight > 0)
    motRightForward();
  else if(motRight < 0)
    motRightBackward();
}

ISR(TIMER2_COMPA_vect)
{
  motLeftStop();
}

ISR(TIMER2_COMPB_vect)
{
  motRightStop();
}



Теперь, чтобы изменить скорость вращения пропеллера, нам надо сделать 2 действия:

  1. Записать положительное, отрицательное или нулевое значение в motLeft/motRight (модуль не важен);
  2. Записать «скорость вращения» в OCR2A/OCR2В.


Напишем для этого ещё пару функций
void setMotLeft(int8_t v){ // -127..+127
  if(abs(v) < 5) v = 0;
  motLeft = v;  
  OCR2A = abs(v) * 2;
}

void setMotRight(int8_t v){ // -127..+127
  if(abs(v) < 5) v = 0;
  motRight = v;
  OCR2B = abs(v) * 2;
}


Строки
if(abs(v) < 5) v = 0;


нужны чтобы лодка не тратила энергию на попытку крутить винты записывая в OCR2x значение меньшее 5 (они при этом всё равно не крутятся).


Теперь осталось настроить пины МК и таймер
void motInit(){
  DDRD |= (1 << MOT_LEFT_PLUS) | (1 << MOT_LEFT_MINUS) | (1 << MOT_RIGHT_PLUS) | (1 << MOT_RIGHT_MINUS);
  //TCCR2B |= (1 << CS22)|(1 << CS21)|(1 << CS20); // set up timer with prescaler = 1024. Переполнение каждые 16 мс
  //TCCR2B |= (1 << CS22)|(0 << CS21)|(0 << CS20); // set up timer with prescaler = 64. Переполнение каждую 1 мс
  TCCR2B |= (0 << CS22)|(1 << CS21)|(0 << CS20); // set up timer with prescaler = 8. Переполнение 128 мкс
  //TCCR2B |= (0 << CS22)|(0 << CS21)|(1 << CS20); // set up timer with prescaler = 1. Переполнение 16 мкс
  TIMSK2 |= (1 << TOIE2)|(1 << OCIE2A)|(1 << OCIE2B); // enable overflow interrupt
  TCCR2A &= ~(3); // set WGM20 = 0, WGM21 = 0
  TCCR2B &= ~(1 << 3); // set WGM22 = 0
  setMotLeft(0);
  setMotRight(0);
  sei();
}



И можно управлять моторчикам просто вызывая функции setMotLeft (int8_t v) и setMotRight (int8_t v).
Но мы же хотим управлять лодкой не так! Мы хотим давать команды типа «вперёд/назад» и «вправо/влево»! И пусть она сама разбирается, какие пропеллеры ей для этого куда придётся крутить. Более того, хочется, чтобы лодка сама компенсировала поворачивающее действие ветра, течения и… криво поставленных пропеллеров!
Пойдём теперь с другой стороны. Со стороны ПДУ. В простейшем случае алгоритм его работы следующий:

  1. При включении питания запомнить начальное положение джойстика;
  2. В цикле считывать положение джойстика, вычитать из него нулевое положение и отправлять данные на лодку.


Наш радиомодуль поддерживает пакеты до 32 байт. Чтобы не запоминать смещения, будем использовать запись

struct ControlStatus{
  int16_t x,y;
}  controlStatus;


Следующим образом
    uint8_t packet[MAX_BUFF]; 
    memset(packet, 0, MAX_BUFF);    
    controlStatus.x = (int16_t)analogRead(1) - x0;
    controlStatus.y = (int16_t)analogRead(0) - y0;
    memcpy(packet, &controlStatus, sizeof(controlStatus));
    Mirf.send(packet);
    while(Mirf.isSending()){;};



На стороне приёмника объявим точно такую же запись и

будем её заполнять
  while (Mirf.dataReady()) { 
    uint8_t data[MAX_BUFF];
    Mirf.getData(data);
    memcpy(&controlStatus, data, sizeof(controlStatus));
    setMotRot(-controlStatus.x);
    setMotForward(controlStatus.y); 
  }



В функциях setMotRot и setMotForward

запишем значения в глобальные переменные motRot и motForward
void setMotRot(int16_t v){
  if(abs(v)<10) v = 0;
  motRot = (int32_t)v;
}
void setMotForward(int16_t v){
  if(abs(v)<10) v = 0;
  motForward = (int32_t)v;
}



И перейдём к самому интересному. К тому, как преобразовать «поворачивать налево со скоростью 5 градусов в секунду и чуть-чуть двигаться вперёд!» в «левый двигатель 10% назад, правый 20% вперёд!».
Про то, что такое ПИД регуляторы написано достаточно много. Я использовал для вращения всего две составляющие:

  1. Пропорциональную;
  2. Интегральную.


А для движения вперёд-назад регулятор не стал использовать.
Разберём на примере:

int32_t iDeltaRot = 0;
void motTick(){
  int32_t rot = getRotAvg(); // получаем реальную скорость вращения лодки
  int32_t deltaRot = rot - motRot * rotMaxSpeed / 512;
  iDeltaRot += deltaRot;
  int32_t motRight = (int32_t)motForward * forwardMult - deltaRot * rotMult - iDeltaRot * iDeltaRotMult, 
          motLeft  = (int32_t)motForward * forwardMult + deltaRot * rotMult + iDeltaRot * iDeltaRotMult;  

  int32_t motMax = max(abs(motRight), abs(motLeft));
  if(motMax > 127){
    motRight = (int32_t)motRight * 127 / motMax;
    motLeft  = (int32_t)motLeft  * 127 / motMax;
  }
  
  setMotRight(motRight);
  setMotLeft(motLeft);
}


Код упростил, чтобы сконцентрировать внимание на важных частях, в архиве будет полная версия.
Что же мы тут делаем?

  1. Вычисляем разницу между реальной скоростью вращения лодки (rot) и желаемой (motRot * rotMaxSpeed);
  2. Вычисляем желаемые скорости вращения винтов motRight и motLeft;
  3. Если желаемые скорости вращения превышают максимально возможные, уменьшаем их сохраняя соотношение между ними;
  4. Вызываем уже знакомые нам setMotRight/setMotLeft.


Всё!
Это весь алгоритм управления лодкой!
Сложно? Мне кажется, нет. Но, тем не менее, в ходе испытаний и настроек возникала целая куча проблем, которая привела бы к многочисленным крушениям, если бы это был летательный аппарат.

В описанной функции есть 4 коэффициента:

  1. forwardMult — чувствительность к движению джойстика вперёд/назад;
  2. rotMaxSpeed — желаемая скорость поворота при наклоне джойстика до упора вправо/влево;
  3. rotMult — коэффициент пропорциональной составляющей (насколько отклонение текущей скорости вращения от желаемой влияет на поворот);
  4. iDeltaRotMult — коэффициент интегральной составляющей (насколько отклонение текущего угла разворота от желаемого влияет на поворот).


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

Индикация состояния


При отладке/настройке будут возникать непонятки из серии «почему лодка реагирует на джойстик не так, как мне этого хочется?». Некоторые моменты можно отладить, выводя отладочную информацию на ПК, но было бы удобнее прямо на месте понимать, что происходит. Вначале я рассматривал 2 варианта:

  1. Ноутбук;
  2. Дисплей Nokia 5110
    a23fc565d564418bad9c2ad6a1144f0c.jpg


Недостатки и того, и другого понятны: ноутбук большой и его неудобно с собой таскать, а дисплей Nokia 5110 не позволит наглядно отобразить одновременно большое количество параметров состояния лодки.

Я взял нечто среднее между ними: Nextion Enhanced NX4827K043 — Generic 4.3'' HMI Touch Display. Благодаря сенсорному дисплею, можно прям на ходу быстро и удобно настраивать параметры лодки. Это своеобразный компьютер состоящий из:

  1. Микроконтроллера GD32F103R8T6;
  2. SDRAM Winbond W9864G6KH-6 (8 МБ);
  3. Флеш памяти Winbond W25Q256FVFG (32 МБ, 100 000 циклов перезаписи, что весьма радует);
  4. ПЛИС Altera MAX II EPM570T144C5N.


Всё в сборе выглядит вот так (кликабельно):
b78b67ad2ffd4f00a0e82daae979dad8.JPG

Является этот компьютер/дисплей чёрным ящиком и заточен на взаимодействие с человеком. Даже имеющийся GPIO заточен для подключения кнопок и индикаторов. Их же Expansion Board это подтверждает. Так что использовать встроенный контроллер в качестве пульта управления лодкой (считывать показания джойстика, обмениваться данными с радиомодулем NRF24L01+) не получится. Для взаимодействия с микроконтроллером есть UART и… и всё.

О том как и что можно делать при помощи этого дисплея можно делать написано куча + есть ролики на Youtube. Посмотрите, например, вот это — там всё понятно показано. Но так как этот дисплей стоит дороже, чем все остальные компоненты лодки и пульта вместе взятые, опишу подробнее свои впечатления от работы с ним. Возможно, это кому-то поможет понять подходит ли ему этот вариант или ноутбук/дисплей Nokia 5110 будут предпочтительнее. Достоинства Nextion Enhanced NX4827K043:

  1. Очень легко использовать. Есть простая документация, примеры, ролики на Youtube, … За пару часов можно разобраться с нуля. Почти всё, что надо про него знать находится на двух страницах: вики страничка и Instruction Set
  2. Очень быстрая разработка. Визуальный редактор по типу Visual Studio (только проще). Накидал компонентов и всё работает.
  3. Весьма качественные компоненты. Та же флеш память на 100k циклов перезаписи.
  4. Отладчик, который может имитировать дисплей при помощи ПК и общаться с вашим МК через COM порт. Позволяет полностью разработать устройство, отладить и покупать дисплей, только если всё устроит.
    Хотя есть с ним проблема

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

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


Недостатки:

  1. Цена. $50 всё-таки очень много для 4.3'' дисплея.
  2. Существующих компонентов мало, настроек компонентов мало, как создавать свои непонятно. Частично это компенсируется функциями рисования примитивов (линии, прямоугольники, круги, …).
  3. Стандартный компонент Gauge мерцает при обновлении.
  4. Нет (по крайней мере, я не нашёл) прозрачности.
  5. Требования к источнику питания: 4.75–7 В и средний ток 250 мА. При просадке напряжения, дисплей начинает мигать.
  6. Только UART. Могли бы сделать общение с ним ещё по SPI и I²C.
  7. Вывод GPIO только под шлейф (нет гребёнки 2,54 мм), нет АЦП.

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

Дисплей может выполнить сразу две задачи:

  1. Индикация состояния. Меня в первую очередь интересуют:
    • «Скорости вращения» винтов;
    • Значение переменной iDeltaRot — насколько желаемый угол разворота отличается от желаемого;
    • Скорость вращения лодки;
    • Угол поворота лодки;
    • Частота получения пакетов от пульта;
    • Частота вызовов функции motTick.

  2. Настройка параметров, а именно описанных выше forwardMult, rotMaxSpeed, rotMult, iDeltaRotMult.

Сделал 2 странички (кликабельны для оценки качества):

  1. Индикации:
    6f7aaa57152e48a5b92e6555d230d863.JPG
  2. Настройки параметров:
    2569f622dc234426b0dc8d4352a8488e.JPG
    Первые 4 колонки слева направо: forwardMult, rotMult, iDeltaRotMult, rotMaxSpeed.

Видео теста лодки на полу:

Реакция лодки на внешнее разворачивающее воздействие при различных iDeltaRotMult (интегральных коэффициентах):

Демонстрация влияния параметров на воде:

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

Характеристики


  • Тяга 9 г;
  • Масса 115 г, из которых аккумуляторы весят 52 г;
  • Максимальное ускорение получаем в 0.77 м/с^2. До человеческих 5 км/ч, если бы не было сопротивления воды, лодка бы разгонялась за 1.8 с;
  • Стоимость компонентов около $15 если использовать Arduino Nano и в пульте и в лодке (без дисплея и аккумуляторов).

Заключение


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

Архив с проектами

Ну и напоследок, чтобы было к чему стремиться, вот видео удивительного аппарата:

© Geektimes