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

«Мы можем исключительно много, но до сих пор мы так и не поняли, что из того, что мы можем, нам действительно нужно» © АБС, «Улитка на склоне»

Приветствую вас, глубокоуважаемые!

Мы тут опять решили проблему, о существовании которой вы скорее всего и не подозревали: сделали хоббийные модули для передачи и приема 4 управляющих команд через воду на дальность (и глубину) до 300 метров. 

Видео испытаний лодки на акустическом управлении и подрыва фейерверка по акустическому сигналу через воду, а также зачем, почему и как — под катом.  

У нас идея фикс: нести гидроакустику из узкого круга ограниченных людей в широкие массы. 

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

Рефлексивное вступление

Я все детство мечтал о машинке на радиоуправлении, пока, наконец не наигрался вдоволь с подарком для своего, тогда еще трехлетнего, сына. Пока он ее не сломал.

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

Да что я такое говорю? Будущее настало и дроны, бороздя воздух, во всю развозят посылки! А как только мы лезем под воду — сразу начинаются толстенные провода, чемоданы и колючий ценник на проводную камеру с моторчиками. Кажется сейчас это называют подводным роботом.

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

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

От идеи до реализации

Как это обычно бывает, сначала речь шла просто о дистанционном »включателе». 

Это должны были быть два простых устройства: приемник и передатчик. Разделенные сотней — другой метров воды, приемник и передатчик должны быть связаны почти как квантово запутанные частицы: нажатая на передатчике кнопка должна была менять состояние цифрового выхода на приемнике.

Согласно этой идее придумали и название: uSwitch. Это вроде как и »you switch» и »underwater switch».

Техническая концепция состояла в том, чтобы передавать короткий (пару-тройку миллисекунд) тональный сигнал. 

Передавать — в акустике — это меньшее из зол. Передать можно почти что угодно, гораздо сложнее — принять.

По прикидкам, любимый 8-битный малютка моего коллеги, 25 МГц C8051F300, вполне справлялся с детектированием какого-нибудь одного тона, с частотой в удобной для нас полосе 20–30 кГц.

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

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

Сначала передаем стартовый тон на одной частоте, а на другой частоте, через задержку связанную с номером кода, передаем стоповый тон.

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

Ограниченное количеством оставшихся свободных у контроллера ног, число кодов в первой версии платы стало равным четырем

Вот так выглядят платы приемника и передатчика. Они имеют одинаковые посадочные размеры и одинаковое положение разъемов и контактных площадок.

Модуль передатчика uSwitch TXМодуль передатчика uSwitch TXМодуль приемника uSwitch RXМодуль приемника uSwitch RX

На обоих платах есть пины STROBE, BIT 0 и BIT 1. На передатчике — это цифровые входы, а на приемнике — цифровые выходы с открытым коллектором. 

По умолчанию все везде притянуто к единице. Активное состояние — Low.

Чтобы передать один из четырех кодов:

  • выставляем пины BIT 0 и BIT 1 в нужное нам состояние

  • вешаем пин STROBE на землю

Такой способ позволяет подключить к передатчику простую кнопку и пару тумблеров, обходясь даже без Ардуино. Например, вот так:

Импровизированный стенд передатчикаИмпровизированный стенд передатчика

На приемнике все также, только наоборот: когда пин STROBE перейдет в состояние цифрового нуля, нужно считать состояние пинов BIT 0 и BIT1. Тут тоже можно не подключать никакую Ардуину и обойтись релюшками и светодиодами, если есть такая необходимость.

Модули питаются в диапазоне от 5 до 15V, потребляют по 20 мА в приеме/ожидании и 500 мА при излучении.

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

Внимательный читатель сразу скажет: да что ты мне рассказываешь? Вон же трансформатор на плате!

Скажет — и станет жертвой картинки, ибо на плате вовсе не трансформатор, а простой дроссель

Давайте я немного поясню:  

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

Трансформатор это более сложное моточное изделие, чем дроссель, от которого требуется только заданная индуктивность. Дроссель — значительно более технологичная штука, особенно если мотаешь вручную.

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

Трансформатор мы заменяем гораздо более простым дросселем и вот как это работает в общих чертах:

К объяснению того, как дроссель заменяет трансформатор в усилителеК объяснению того, как дроссель заменяет трансформатор в усилителе

Если на ключ (в нашем случае MOSFET с напряжением сток-исток не менее 200 Вольт) подать высокий уровень, но он замкнется на землю. Через индуктивность начнет нарастать ток. Нарастать он будет до момента закрытия ключа. Режим насыщения индуктивности мы исключаем подбором ширины импульса. Из школьной физики мы помним, что энергия запасенная в индуктивности равна L*I^2/2. После закрытия ключа индуктивность начинает передавать энергию в антенну. Поскольку антенна по сути конденсатор, энергия в котором определяется как C*U^2/2, то получаем 2–3 кратное повышение напряжения. Если перед антенной поставить диод, то получилась бы типовая схема повышающего DC-DC преобразователя.

И да, кстати, частоту сигнала регулируем периодом T.

Надеюсь, я не сильно утомил читателя деталями. Честное слово: дальше веселые картинки и видео.

Первые испытания

Первые испытания мы проводили еще на льду Волго-Донского канала в январе этого (2021) года.

В первой фазе баловались дистанционным включением светодиода. Чтобы просто попробовать как далеко оно работает. Питали все от свинцовых АКБ на 4 А*ч. Светодиод мощный, и включался при помощи реле.

Мы специально приехали на водоем еще затемно, чтобы моргание светодиода и особенно «вторую фазу» эксперимента было лучше видно. Наивные, мы не знали еще тогда что забыли самый главный инструмент зимних испытаний — ледобур. В итоге двое оставшихся знатно подмерзли, а двое поехавших за ледобуром накатали еще 80 км туда — обратно. До рассвета мы не успели, но очень пасмурная погода кое-как спасла ситуацию.

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

0d6debef768839da1466b73c8cc74cb9.jpeg

Во второй фазе предполагалось дистанционно поджечь фейерверк. Все, что могло пойти «не так» — пошло «не так». Все, кроме акустики: с ней никаких проблем не возникло.

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

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

Вид с «пункта управления»:

Репортаж непосредственно с мета «события»:

Успех предприятия закрепили кофе на льду:

347866604cf3462143e1f7441d28c679.jpeg

НЕрадиоуправляемая лодка

Далее следовало как-то наглядно продемонстрировать работу системы в сценариях и условиях, максимально приближенных к предполагаемым условиям эксплуатации.

Нам показалось разумным сделать надводную лодку на дистанционном акустическом управлении:

  1. Во-первых, в отличие от автономного подводного аппарата (которого все равно нет в наличии) ее видно

  2. Во-вторых, все доступные нам водоемы все равно мутные, поэтому см. п. 1.

В качестве исходного материала взяли приманочную лодку (Bait boat), из которой удалили все внутренности, кроме двигателей:

A - АкустическийA — Акустический

Ардуина здесь исключительно для перевода принятого от uSwitch RX кода в сигнал управления двигателями. Родной АКБ тоже пришлось заменить — он подозрительно вспух. Поставили 4 аккумулятора 18650 в параллель. На всякий случай под спойлером нехитрый скетч:

Boat brain sketch

#define CMD_DURATION_MS      (3000)

#define USW_ACTIVE_STATE     (LOW)
#define USW_INACTIVE_STATE   (HIGH)
#define USW_INTERRUPT_EDGE   (FALLING)
#define USW_STROBE_PIN       (2)
#define USW_BIT0_PIN         (3)
#define USW_BIT1_PIN         (4)

#define MOTOR_ACTIVE_STATE   (HIGH)
#define MOTOR_INACTIVE_STATE (LOW)
#define MOTOR_LEFT_PIN       (5)
#define MOTOR_RIGHT_PIN      (6)

#define LED_ACTIVE_STATE     (LOW)
#define LED_INACTIVE_STATE   (HIGH)
#define LED_LEFT_PIN         (7)
#define LED_RIGHT_PIN        (8)

#define CMD_STOP             (0)
#define CMD_TURN_RIGHT       (1)
#define CMD_TURN_LEFT        (2)
#define CMD_FULL_SPEED_AHEAD (3)

byte usw_code = 0;
volatile bool usw_strobe = false;

byte cmd = CMD_STOP;
long cmd_tstamp = 0;
bool cmd_active = false;

long ticks = 0;

byte usw_ReadCode()
{
  byte result = 0;
  result = digitalRead(USW_BIT0_PIN);
  result = result << 1;
  result |= digitalRead(USW_BIT1_PIN);
  return result;
}

void usw_OnStrobe()
{
  if (!usw_strobe)
  {
    usw_strobe = true;
  }
}

void cmd_Run(byte new_cmd)
{
  if ((cmd_active) && (cmd == new_cmd))
  {
    cmd_tstamp = millis();    
  }
  else
  {  
    byte left_motor_state = MOTOR_INACTIVE_STATE;
    byte right_motor_state = MOTOR_INACTIVE_STATE;
    byte left_led_state = LED_INACTIVE_STATE;
    byte right_led_state = LED_INACTIVE_STATE;
    
    if (new_cmd == CMD_STOP)
    {
      left_motor_state = MOTOR_INACTIVE_STATE;
      right_motor_state = MOTOR_INACTIVE_STATE;
      left_led_state = LED_INACTIVE_STATE;
      right_led_state = LED_INACTIVE_STATE;
    }
    else if (new_cmd == CMD_TURN_LEFT)
    {
      left_motor_state = MOTOR_INACTIVE_STATE;
      right_motor_state = MOTOR_ACTIVE_STATE;
      left_led_state = LED_ACTIVE_STATE;
      right_led_state = LED_INACTIVE_STATE;
    }
    else if (new_cmd == CMD_TURN_RIGHT)
    {
      left_motor_state = MOTOR_ACTIVE_STATE;
      right_motor_state = MOTOR_INACTIVE_STATE;
      left_led_state = LED_INACTIVE_STATE;
      right_led_state = LED_ACTIVE_STATE;
    }
    else if (new_cmd == CMD_FULL_SPEED_AHEAD)
    {
      left_motor_state = MOTOR_ACTIVE_STATE;
      right_motor_state = MOTOR_ACTIVE_STATE;
      left_led_state = LED_ACTIVE_STATE;
      right_led_state = LED_ACTIVE_STATE;
    }
    else
    {
      // ???
    }
    
    digitalWrite(MOTOR_LEFT_PIN, left_motor_state);
    digitalWrite(MOTOR_RIGHT_PIN, right_motor_state);
    digitalWrite(LED_LEFT_PIN, left_led_state);
    digitalWrite(LED_RIGHT_PIN, right_led_state);
    cmd_tstamp = millis();
    cmd_active = true;
    cmd = new_cmd;
  }
}

void cmd_Idle()
{
  digitalWrite(MOTOR_LEFT_PIN, MOTOR_INACTIVE_STATE);
  digitalWrite(MOTOR_RIGHT_PIN, MOTOR_INACTIVE_STATE);
  digitalWrite(LED_LEFT_PIN, LED_INACTIVE_STATE);
  digitalWrite(LED_RIGHT_PIN, LED_INACTIVE_STATE);
  cmd_active = false;
}

void setup() 
{  
  pinMode(USW_STROBE_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(USW_STROBE_PIN), usw_OnStrobe, USW_INTERRUPT_EDGE);
  pinMode(USW_BIT0_PIN, INPUT_PULLUP);
  pinMode(USW_BIT1_PIN, INPUT_PULLUP);

  pinMode(MOTOR_LEFT_PIN, OUTPUT);
  pinMode(MOTOR_RIGHT_PIN, OUTPUT);
  digitalWrite(MOTOR_LEFT_PIN, MOTOR_INACTIVE_STATE);
  digitalWrite(MOTOR_RIGHT_PIN, MOTOR_INACTIVE_STATE);

  pinMode(LED_LEFT_PIN, OUTPUT);
  pinMode(LED_RIGHT_PIN, OUTPUT);
  digitalWrite(LED_LEFT_PIN, LED_INACTIVE_STATE);
  digitalWrite(LED_RIGHT_PIN, LED_INACTIVE_STATE);

  int delay_duration_ms = 500;

  while (delay_duration_ms > 0)
  {
    digitalWrite(LED_LEFT_PIN, LED_ACTIVE_STATE);
    digitalWrite(LED_RIGHT_PIN, LED_ACTIVE_STATE);
    delay(delay_duration_ms);
    digitalWrite(LED_LEFT_PIN, LED_INACTIVE_STATE);
    digitalWrite(LED_RIGHT_PIN, LED_INACTIVE_STATE);
    delay(delay_duration_ms);    
    delay_duration_ms -= 25;
  }
}

void loop() 
{
  if (usw_strobe)
  {
    usw_code = usw_ReadCode();
    cmd_Run(usw_code);
    usw_strobe = false;    
  }

  if (cmd_active)
  {
    ticks = millis();
    if (ticks - cmd_tstamp > CMD_DURATION_MS)
    {      
      cmd_Idle();
      cmd_active = false;
    }
  }
}

В качестве приемной антенны использована антенна из пьезопищалки:

6898600cfb5dfd34e9b8d924c911f580.jpeg

Пульт управления лодкой собрали наспех в первой попавшейся коробочке:

Значки нарисованы маркером =)Значки нарисованы маркером =)

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

ca28e60efff95e7b91461a30252d7ef5.jpeg

Ардуина здесь для преобразования нажатия кнопок в код. Под спойлером не менее незатейливый скетч:

Remote sketch

#define BTN_FULL_STOP_PIN        (5)
#define BTN_TURN_LEFT_PIN        (2)
#define BTN_TURN_RIGHT_PIN       (4)
#define BTN_FULL_SPEED_AHEAD_PIN (3)

#define BTN_ACTIVE_STATE         (LOW)
#define BTN_INACTIVE_STATE       (HIGH)

#define SW_STROBE_PIN            (6)
#define SW_BIT_0_PIN             (7)
#define SW_BIT_1_PIN             (8)

#define SW_ACTIVE_STATE          (LOW)
#define SW_INACTIVE_STATE        (HIGH)

#define DEBOUNCE_MS              (300)

#define CMD_STOP             (0)
#define CMD_TURN_RIGHT       (1)
#define CMD_TURN_LEFT        (2)
#define CMD_FULL_SPEED_AHEAD (3)

long ts_tick = 0;
long tick = 0;

void cmd_Write(byte cmd)
{  
  digitalWrite(SW_BIT_1_PIN, cmd & 2);
  digitalWrite(SW_BIT_0_PIN, cmd & 1);
  digitalWrite(SW_STROBE_PIN, SW_ACTIVE_STATE);
  delay(100);
  
  digitalWrite(SW_BIT_1_PIN, SW_INACTIVE_STATE);
  digitalWrite(SW_BIT_0_PIN, SW_INACTIVE_STATE);
  digitalWrite(SW_STROBE_PIN, SW_INACTIVE_STATE);  
}

void setup() {
  
  pinMode(BTN_FULL_STOP_PIN, INPUT_PULLUP);
  pinMode(BTN_TURN_LEFT_PIN, INPUT_PULLUP);
  pinMode(BTN_TURN_RIGHT_PIN, INPUT_PULLUP);
  pinMode(BTN_FULL_SPEED_AHEAD_PIN, INPUT_PULLUP);

  pinMode(SW_STROBE_PIN, OUTPUT);
  pinMode(SW_BIT_0_PIN, OUTPUT);
  pinMode(SW_BIT_1_PIN, OUTPUT);

  digitalWrite(SW_STROBE_PIN, SW_INACTIVE_STATE);
  digitalWrite(SW_BIT_0_PIN, SW_INACTIVE_STATE);
  digitalWrite(SW_BIT_0_PIN, SW_INACTIVE_STATE);
}

void loop() {

  tick = millis();
  
  if (tick - ts_tick >= DEBOUNCE_MS)
  {      
      if (digitalRead(BTN_FULL_STOP_PIN) == BTN_ACTIVE_STATE)
      {
        cmd_Write(CMD_STOP);
      }
      else if (digitalRead(BTN_TURN_LEFT_PIN) == BTN_ACTIVE_STATE)
      {
        cmd_Write(CMD_TURN_LEFT);
      }
      else if (digitalRead(BTN_TURN_RIGHT_PIN) == BTN_ACTIVE_STATE)
      {
        cmd_Write(CMD_TURN_RIGHT);
      }
      else if (digitalRead(BTN_FULL_SPEED_AHEAD_PIN) == BTN_ACTIVE_STATE)
      {
        cmd_Write(CMD_FULL_SPEED_AHEAD);
      }
      
      ts_tick = tick;
  }

}

Питается все от 9-вольтовой «Кроны». Забегая вперед, скажу, что этого хватило как минимум на 120 метров. Дальше не проверяли — лодку просто не видно издалека, и понять, куда она там едет практически невозможно.

А вот так это работает в жизни. Место действия — наш любимый пруд Южный.

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

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

Неисправность парировали при помощи догрузки куском кирпича, так сказать «in situ».

Напоследок короткое видео, на котором лодка запечатлена на максимальном удалении от «судоводителя» — порядка 120–130 метров:

В сухом остатке

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

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

Благодарю вас за внимание! Также я и мои коллеги будем искренне благодарны за сообщения об ошибках, вопросы и интересную дискуссию.

© Habrahabr.ru